购物车与结算区域的功能实现与优化
# 1. 购物车页面
# 1.1 创建购物车页面的编译模式
打开微信开发者工具,点击工具栏上的“编译模式”下拉菜单,选择“添加编译模式”:
勾选“启动页面的路径”之后,点击“确定”按钮,新增购物车页面的编译模式:
# 1.2 创建收货地址组件
在
components
目录上鼠标右键,选择新建组件
,并填写组件相关的信息:渲染收货地址组件的基本结构:
<view> <!-- 选择收货地址的盒子 --> <view class="address-choose-box"> <button type="primary" size="mini" class="btnChooseAddress">请选择收货地址+</button> </view> <!-- 渲染收货信息的盒子 --> <view class="address-info-box"> <view class="row1"> <view class="row1-left"> <view class="username">收货人:<text>escook</text></view> </view> <view class="row1-right"> <view class="phone">电话:<text>138XXXX5555</text></view> <uni-icons type="arrowright" size="16"></uni-icons> </view> </view> <view class="row2"> <view class="row2-left">收货地址:</view> <view class="row2-right">河北省邯郸市肥乡区xxx 河北省邯郸市肥乡区xxx 河北省邯郸市肥乡区xxx 河北省邯郸市肥乡区xxx </view> </view> </view> </view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24美化收货地址组件的样式:
// 底部边框线的样式 .address-border { display: block; width: 100%; height: 5px; } // 选择收货地址的盒子 .address-choose-box { height: 90px; display: flex; align-items: center; justify-content: center; } // 渲染收货信息的盒子 .address-info-box { font-size: 12px; height: 90px; display: flex; flex-direction: column; justify-content: center; padding: 0 5px; // 第一行 .row1 { display: flex; justify-content: space-between; .row1-right { display: flex; align-items: center; .phone { margin-right: 5px; } } } // 第二行 .row2 { display: flex; align-items: center; margin-top: 10px; .row2-left { white-space: nowrap; } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 1.3 收货地址按需展示
在 data 中定义收货地址的信息对象:
export default { data() { return { // 收货地址 address: {}, } }, }
1
2
3
4
5
6
7
8使用
v-if
和v-else
实现按需展示:<!-- 选择收货地址的盒子 --> <view class="address-choose-box" v-if="JSON.stringify(address) === '{}'"> <button type="primary" size="mini" class="btnChooseAddress">请选择收货地址+</button> </view> <!-- 渲染收货信息的盒子 --> <view class="address-info-box" v-else> <!-- 省略其它代码 --> </view>
1
2
3
4
5
6
7
8
9
# 1.4 实现选择收货地址
为
请选择收货地址+
的button
按钮绑定点击事件处理函数:<!-- 选择收货地址的盒子 --> <view class="address-choose-box" v-if="JSON.stringify(address) === '{}'"> <button type="primary" size="mini" class="btnChooseAddress" @click="chooseAddress">请选择收货地址+</button> </view>
1
2
3
4定义
chooseAddress
事件处理函数,调用小程序提供的chooseAddress()
API 实现选择收货地址的功能:methods: { // 选择收货地址 async chooseAddress() { // 1. 调用小程序提供的 chooseAddress() 方法,即可使用选择收货地址的功能 // 返回值是一个数组:第 1 项为错误对象;第 2 项为成功之后的收货地址对象 const [err, succ] = await uni.chooseAddress().catch(err => err) // 2. 用户成功的选择了收货地址 if (err === null && succ.errMsg === 'chooseAddress:ok') { // 为 data 里面的收货地址对象赋值 this.address = succ } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14微信公告 (opens new window) 配置链接 (opens new window)
若使用以上接口,均需在小程序管理后台,「开发」-「开发管理」-「接口设置」 (opens new window)中自助开通该接口权限。
/* 小程序特有相关 */ "mp-weixin": { "appid": "wx6d8be68bfb6cb5fc", "setting": { "urlCheck": false }, "usingComponents": true, "requiredPrivateInfos": [ "getFuzzyLocation", "getLocation", "onLocationChange", "startLocationUpdateBackground", "chooseAddress" ] },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15定义收货详细地址的计算属性:
computed: { // 收货详细地址的计算属性 addstr() { if (!this.address.provinceName) return '' // 拼接 省,市,区,详细地址 的字符串并返回给用户 return this.address.provinceName + this.address.cityName + this.address.countyName + this.address.detailInfo } }
1
2
3
4
5
6
7
8
9渲染收货地址区域的数据:
<!-- 渲染收货信息的盒子 --> <view class="address-info-box" v-else> <view class="row1"> <view class="row1-left"> <view class="username">收货人:<text>{{address.userName}}</text></view> </view> <view class="row1-right"> <view class="phone">电话:<text>{{address.telNumber}}</text></view> <uni-icons type="arrowright" size="16"></uni-icons> </view> </view> <view class="row2"> <view class="row2-left">收货地址:</view> <view class="row2-right">{{addstr}}</view> </view> </view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1.5 存储到 vuex 中
在
store
目录中,创建用户相关的vuex
模块,命名为user.js
:export default { // 开启命名空间 namespaced: true, // state 数据 state: () => ({ // 收货地址 address: {}, }), // 方法 mutations: { // 更新收货地址 updateAddress(state, address) { state.address = address }, }, // 数据包装器 getters: {}, }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21在
store/store.js
模块中,导入并挂载user.js
模块:// 1. 导入 Vue 和 Vuex import Vue from 'vue' import Vuex from 'vuex' // 导入购物车的 vuex 模块 import moduleCart from './cart.js' // 导入用户的 vuex 模块 import moduleUser from './user.js' // 2. 将 Vuex 安装为 Vue 的插件 Vue.use(Vuex) // 3. 创建 Store 的实例对象 const store = new Vuex.Store({ // TODO:挂载 store 模块 modules: { // 挂载购物车的 vuex 模块,模块内成员的访问路径被调整为 m_cart,例如: // 购物车模块中 cart 数组的访问路径是 m_cart/cart m_cart: moduleCart, // 挂载用户的 vuex 模块,访问路径为 m_user m_user: moduleUser, }, }) // 4. 向外共享 Store 的实例对象 export default store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25改造
address.vue
组件中的代码,使用 vuex 提供的 address 计算属性 替代 data 中定义的本地 address 对象:// 1. 按需导入 mapState 和 mapMutations 这两个辅助函数 import { mapState, mapMutations } from 'vuex' export default { data() { return { // 2.1 注释掉下面的 address 对象,使用 2.2 中的代码替代之 // address: {} } }, methods: { // 3.1 把 m_user 模块中的 updateAddress 函数映射到当前组件 ...mapMutations('m_user', ['updateAddress']), // 选择收货地址 async chooseAddress() { const [err, succ] = await uni.chooseAddress().catch((err) => err) // 用户成功的选择了收货地址 if (err === null && succ.errMsg === 'chooseAddress:ok') { // 3.2 把下面这行代码注释掉,使用 3.3 中的代码替代之 // this.address = succ // 3.3 调用 Store 中提供的 updateAddress 方法,将 address 保存到 Store 里面 this.updateAddress(succ) } }, } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 1.6 user数据持久化
修改
store/user.js
模块中的代码如下:export default { // 开启命名空间 namespaced: true, // state 数据 state: () => ({ // 收货地址 address: uni.getStorage('address') || {}, }), // 方法 mutations: { // 更新收货地址 updateAddress(state, address) { state.address = address setStorage('address', address) }, }, // 数据包装器 getters: {}, }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1.7 将 addstr 抽离为 getters
目的:为了提高代码的复用性,可以把收货的详细地址抽离为 getters,方便在多个页面和组件之间实现复用。
剪切
my-address.vue
组件中的addstr
计算属性的代码,粘贴到user.js
模块中,作为一个 getters 节点:// 数据包装器 getters: { // 收货详细地址的计算属性 addstr(state) { if (!state.address.provinceName) return '' // 拼接 省,市,区,详细地址 的字符串并返回给用户 return state.address.provinceName + state.address.cityName + state.address.countyName + state.address.detailInfo } }
1
2
3
4
5
6
7
8
9
10改造
my-address.vue
组件中的代码,通过mapGetters
辅助函数,将m_user
模块中的addstr
映射到当前组件中使用:// 按需导入 mapGetters 辅助函数 import { mapState, mapMutations, mapGetters } from 'vuex' export default { // 省略其它代码 computed: { ...mapState('m_user', ['address']), // 将 m_user 模块中的 addstr 映射到当前组件中使用 ...mapGetters('m_user', ['addstr']), }, }
1
2
3
4
5
6
7
8
9
10
11
# 1.8 重新选择收货地址
为 class 类名为
address-info-box
的盒子绑定click
事件处理函数如下:<!-- 渲染收货信息的盒子 --> <view class="address-info-box" v-else @click="chooseAddress"> <!-- 省略其它代码 --> </view>
1
2
3
4
# 1.9 渲染商品列表区域的基本结构
通过
mapState
辅助函数,将 Store 中的cart
数组映射到当前页面中使用:import badgeMix from '@/mixins/tabbar-badge.js' // 按需导入 mapState 这个辅助函数 import { mapState } from 'vuex' export default { mixins: [badgeMix], computed: { // 将 m_cart 模块中的 cart 数组映射到当前页面中使用 ...mapState('m_cart', ['cart']), }, data() { return {} }, }
1
2
3
4
5
6
7
8
9
10
11
12
13
14渲染购物车列表
<van-card v-for="item in cart" :key="item.goods_id" :price="item.goods_price" :title="item.goods_name"> <view class="custom-thumb" slot="thumb"> <radio :checked="item.goods_state" color="#c00000"> </radio> <image :src="item.goods_small_logo"></image> </view> </van-card>
1
2
3
4
5
6
7样式
.custom-thumb { display: flex; height: 100%; align-items: center; image { width: 150rpx; height: 200rpx; } }
1
2
3
4
5
6
7
8
9
# 2. 结算区域
# 2.1 把结算区域封装为组件
在
components
目录中,新建my-settle
结算组件:初始化
my-settle
组件的基本结构和样式:<template> <!-- 最外层的容器 --> <view class="my-settle-container"> 结算组件 </view> </template> <script> export default { data() { return {} }, } </script> <style lang="scss"> .my-settle-container { /* 底部固定定位 */ position: fixed; bottom: 0; left: 0; /* 设置宽高和背景色 */ width: 100%; height: 50px; background-color: cyan; } </style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27在
cart.vue
页面中使用自定义的my-settle
组件,并美化页面样式,防止页面底部被覆盖:<template> <view class="cart-container"> <!-- 使用自定义的 address 组件 --> <!-- 购物车商品列表的标题区域 --> <!-- 商品列表区域 --> <!-- 结算区域 --> <my-settle></my-settle> </view> </template> <style lang="scss"> .cart-container { padding-bottom: 50px; } </style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.2 渲染结算区域的结构和样式
定义如下的 UI 结构:
<!-- 最外层的容器 --> <view class="my-settle-container"> <!-- 全选区域 --> <label class="radio"> <radio color="#C00000" :checked="true" /><text>全选</text> </label> <!-- 合计区域 --> <view class="amount-box"> 合计:<text class="amount">¥1234.00</text> </view> <!-- 结算按钮 --> <view class="btn-settle">结算(0)</view> </view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15美化样式:
.my-settle-container { position: fixed; bottom: 0; left: 0; width: 100%; height: 50px; // 将背景色从 cyan 改为 white background-color: white; display: flex; justify-content: space-between; align-items: center; padding-left: 5px; font-size: 14px; .radio { display: flex; align-items: center; } .amount { color: #c00000; } .btn-settle { height: 50px; min-width: 100px; background-color: #c00000; color: white; line-height: 50px; text-align: center; padding: 0 10px; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 2.3 动态渲染已勾选商品的总数量
在
store/cart.js
模块中,定义一个名称为checkedCount
的 getters,用来统计已勾选商品的总数量:// 勾选的商品的总数量 checkedCount(state) { // 先使用 filter 方法,从购物车中过滤器已勾选的商品 // 再使用 reduce 方法,将已勾选的商品总数量进行累加 // reduce() 的返回值就是已勾选的商品的总数量 return state.cart.filter(x => x.goods_state).reduce((total, item) => total += item.goods_count, 0) }
1
2
3
4
5
6
7在
my-settle
组件中,通过mapGetters
辅助函数,将需要的 getters 映射到当前组件中使用:import { mapGetters } from 'vuex' export default { computed: { ...mapGetters('m_cart', ['checkedCount']), }, data() { return {} }, }
1
2
3
4
5
6
7
8
9
10将
checkedCount
的值渲染到页面中:<!-- 结算按钮 --> <view class="btn-settle">结算({{checkedCount}})</view>
1
2