购物车 - 动态设置tabbar徽标
- 就是在页面一打开时调用
uni.setTabBarBadge
方法进行设置 - 所以来到
pages/cart
页面,导入mapGetters
,然后映射成计算属性,在页面打开时设置tabbar徽标
import { mapGetters } from 'vuex'
export default {
computed:{
...mapGetters('cart',['total'])
},
// 页面加载
onl oad() {
uni.setTabBarBadge({
index:2,
// 要求给的是字符串
text: this.total + ''
})
},
}
购物车 - 将设置徽标代码设置为mixins
- 因为其他三个
tabbar
页面也要能设置,那就意味着这段代码,其他的vue实例也要用,因此可以用mixins
进行混入 - 来到项目根目录,新建
mixins
文件夹,里面放一个tabbar-badge.js
代码如下
import {
mapGetters
} from 'vuex'
export default {
computed: {
...mapGetters('cart', ['total'])
},
// 页面显示
onl oad() {
uni.setTabBarBadge({
index: 2,
// 要求给的是字符串
text: this.total + ''
})
},
}
- 再依次来到
home
,cate
,cart
,my
页面,导入和混入
import { mapGetters } from 'vuex'
// 导入混入对象
import tabbarBadge from '../../mixins/tabbar-badge.js'
export default {
mixins:[ tabbarBadge ],
}
购物车 - 商品区域的布局图
购物车 - 商品列表的标题区域
<view class="cart-body">
<view class="title-box">
<uni-icons class="shop-icon" type="shop" size="25"></uni-icons>
<text>购物车</text>
</view>
</view>
.title-box {
padding: 10px;
border-bottom: 1px solid #ddd;
.shop-icon {
margin-right: 10px;
}
text {
vertical-align: middle;
}
}
购物车 - 渲染商品列表
- 经过观察发现,商品列表跟之前
goods_list
里的差不多,所以可以直接把那边的结构和样式都复制过来,只不过v-for换成从vuex里取到的购物车数据来进行v -for
<template>
<view>
<!-- 放主体内容的盒子 -->
<view class="cart-body">
<view class="title-box">
<uni-icons class="shop-icon" type="shop" size="25"></uni-icons>
<text>购物车</text>
</view>
<view class="goods-item" v-for="item in cartList" :key="item.goods_id" @click="toDetail(item)">
<!-- 左边图片 -->
<image class="pic"
:src="item.goods_small_logo || 'https://img3.doubanio.com/f/movie/8dd0c794499fe925ae2ae89ee30cd225750457b4/pics/movie/celebrity-default-medium.png'">
</image>
<!-- 右边的盒子 -->
<view class="info-box">
<text class="title">{{ item.goods_name }}</text>
<text class="price">¥ {{ item.goods_price.toFixed(2) }}</text>
</view>
</view>
</view>
</view>
</template>
- 样式
.goods-item {
display: flex;
margin: 20rpx;
.pic {
width: 260rpx;
height: 260rpx;
margin-right: 10px;
}
.info-box {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
// 单词换行模式,意思是一行不够放,单词打断另起一行
word-break: break-all;
}
.price {
color: #c00;
}
}
}
- 但是有些是中文混排的文字,记得要是英文里的单词折叠换行
- js里记得要映射vuex里的购物车数据
import {
mapState
} from 'vuex'
computed: {
...mapState('cart', ['cartList'])
},
购物车 - 把每一项的点击事件完成
- 购物车里点击商品任何位置要跳转到商品详情,其实就是把
goods_list
里的点击事件赋值过来即可
// 每个商品的点击事件
toDetail(item) {
uni.navigateTo({
url: '/subpkg/goods_detail/goods_detail?goods_id=' + item.goods_id
})
},
购物车 - radio单选框添加至商品列表
- 因为整体是分为一左一右,而左边现在不光要放image,还要放
radio
,所以用一个view
包起来
<view class="pic-box">
<label class="radio">
<radio color="#c00"/>
</label>
<!-- 左边图片 -->
<image class="pic"
:src="item.goods_small_logo || 'https://img3.doubanio.com/f/movie/8dd0c794499fe925ae2ae89ee30cd225750457b4/pics/movie/celebrity-default-medium.png'">
</image>
</view>
- 此时能看到单选框,但是他们没有对齐,所以给这个大盒子加弹性布局,让他们垂直居中
.pic-box {
display: flex;
align-items: center;
......
}
购物车 - radio数据绑定及完成切换选中功能
- radio要通过
checked
设置选中状态,但是不能写死,所以给:checked
然后绑定每一项的goods_checked
,以及,它点击要切换状态,所以意味着要加点击事件,但是如果加了点击事件,因为就有冒泡的存在,所以还会触发父盒子的点击事件,导致跳转了,所以我们要阻止冒泡
<radio color="#c00" @click.stop="setChecked(item)" :checked="item.goods_checked" />
- 点击事件里需要改vuex里的数据,而外面不能直接改,所以回到
store/modules/cart.js
封装一个专门用来改选中状态的mutation
// 专门用来设置选中或不选中的方法
setGoodsChecked(state, goods_id) {
// 找到对应的数据
const item = state.cartList.find(v => v.goods_id === goods_id)
item.goods_checked = !item.goods_checked
}
- 然后回到
cart.vue
的点击事件里调用此方法即可
setChecked(item) {
this.$store.commit('cart/setGoodsChecked', item.goods_id)
},
购物车 - 数字输入框number-box
- 官方文档
- 把它放到跟价格一样的位置,因为又需要这一行一左一右,所以给他们包一个父盒子
<view class="price-box">
<text class="price">¥ {{ item.goods_price.toFixed(2) }}</text>
<uni-number-box @click.native.stop :value="item.goods_count"
@change="changeValue(item, $event)" />
</view>
- 大盒子给弹性布局,然后一左一右
.price-box {
display: flex;
align-items: center;
justify-content: space-between;
........
}
- 解释代码:
- value是用来设置它的数量,数量应该从数据里读取出来
- @change是为了不管是点了+还是-,甚至是直接输入数量,都能触发的事件
@click.native.stop
这里仅仅只是为了阻止冒泡到父盒子进行跳转- 为什么要加
native
?因为这是一个组件,组件没有这个click事件,要用native
加原生上
- 为什么要加
$event
就是这个子组件绑定事件默认传递过来的值
购物车 - 实现数量修改功能
- 因为要修改到
vuex
,所以应该提供一个修改数量的方法,而修改数量要依赖id找到对应的数据,还要依赖数量进行修改,所以意味着要传两个参数,那么应该包成一个对象来传递
// 专门用来修改数量的方法
// 因为既要传id,又要传数量,所以当一个对象传进来
setGoodsCount(state, obj) {
// 首先根据id找到要修改的商品
const item = state.cartList.find(v => v.goods_id === obj.goods_id)
item.goods_count = obj.goods_count
}
- 回到
购物车页面
的上面写的change
事件进行调用修改
// 数量改变触发的事件
changeValue(item, num) {
this.$store.commit('cart/setGoodsCount', {
goods_id: item.goods_id,
goods_count: num
})
},
购物车 - 修改后要存到本地
- 此时发现,不管是打钩还是修改数量,一刷新数据就恢复了,原因是这两个方法改完后没有存到本地
- 解决办法:每次改完都存到本地
// 专门用来设置选中或不选中的方法
setGoodsChecked(state, goods_id) {
.......
// 存到本地存储
uni.setStorageSync('cart', state.cartList)
},
// 专门用来修改数量的方法
// 因为既要传id,又要传数量,所以当一个对象传进来
setGoodsCount(state, obj) {
.......
// 存到本地存储
uni.setStorageSync('cart', state.cartList)
}
购物车 - 动态计算tabbar徽标
- 问题: 当我在购物车里加了或减了数量,tabbar上的徽标不变
- 原因:徽标只在页面打开时设置了一次,后面total总量变了,也没有重新设置徽标
- 解决办法:
- 在mixins里侦听total的改变,一旦改变就重新设置徽标
watch: {
total() {
uni.setTabBarBadge({
index: 2,
text: this.total + ''
})
}
}
购物车 - 如果总数量为0则移除tabbar徽标
- 问题:如果数量为0,徽标上显示为0,而我们要如果为0就不显示徽标,所以要判断
watch: {
total: {
handler() {
if (this.total > 0) {
uni.setTabBarBadge({
index: 2,
text: this.total + ''
})
} else {
// 移除徽标
uni.removeTabBarBadge({
index: 2
})
}
},
immediate: true
}
}
- 建议写成 `immediate`,这样的好处是,onLoad就不用写了,不然onLoad里也要把一样的代码复制一份
购物车 - 滑动操作swipe-action
- 官方文档
- 注意事项:
- 写一个swipe-action里面就要写
swipe-action-item
,有几个item
就代表有几个可以左右滑动 - swipe-action不能放到
swiper
里面 swipe-action-item
有个属性叫right-option
用来设置右侧滑动出来的按钮,加了这个属性就有左滑
- 写一个swipe-action里面就要写
- 因为我们需要每个商品都要有左滑,意味着,要把每个商品都包到
swipe-action
,并且每个商品要有对应的swipe-action-item
,所以v-for应该写到swipe-action-item
身上,改动后的html如下
<uni-swipe-action>
<uni-swipe-action-item :right-options="options" v-for="item in cartList" :key="item.goods_id">
<view class="goods-item" @click="toDetail(item)">
...................
</view>
</uni-swipe-action-item>
</uni-swipe-action>
- 声明data里放按钮
data() {
return {
options: [{
text: '删除',
style: {
backgroundColor: '#c00'
}
}],
};
},
购物车 - 完成删除功能
- 删除要改vuex,要提供一个与之对应的
mutations
做删除- 删除的本质其实就是过滤出要删的元素以外的元素组成新数组,就相当于删除了
// 删除商品
delGoodsById(state, id) {
// 删除一个元素其实就相当于过滤出来除了它以外的元素
// 过滤出id不等于我要删的id的其他元素组成新数组
state.cartList = state.cartList.filter(v => v.goods_id != id)
// 持久化数据
uni.setStorageSync('cart', state.cartList)
}
- 回到
cart.vue
页面,找到侧滑的组件,给它加click事件,因为这个组件内部优化不够好,key最好不要加到它身上,加到子元素
<block v-for="item in cartList">
<uni-swipe-action-item @click="doDel(item.goods_id)" :right-options="options">
<view :key="item.goods_id" class="goods-item" @click="toDetail(item)">
.......
- 点击事件里调用vuex的方法做删除即可
doDel(id) {
// 点谁就删谁
this.$store.commit('cart/delGoodsById', id)
},
购物车 - 收货地址基本布局
- 布局如下
<view>
<view class="add-box">
<view style="display:flex;justify-content: center;align-items: center;height: 180rpx;">
<button size="mini" type="primary">请选择收货地址+</button>
</view>
</view>
<!-- 图片 -->
<view>
<image style="width:100%;height:5px;" src="/static/[email protected]"></image>
</view>
</view>
购物车 - 小程序选择地址功能
- 官方文档
- 自 2022 年 7 月 14 日后发布的小程序,若使用该接口,需要在 app.json 中进行声明,否则将无法正常使用该接口,2022年7月14日前发布的小程序不受影响。声明方法如下
- 原生开发:
- 用法:
wx.chooseAddress()
- app.json,跟
window
平级的地方加如下配置
- 用法:
"requiredPrivateInfos": [
"chooseAddress"
],
- uniapp开发:
-
uni.chooseAddress
-
找到
manifest.json
,打开后找到菜单里的最后一项:“源码视图”,再找到mp-weixin
,加上面的配置即可
-
- 给地址里的按钮加了点击事件
<button @click="chooseAddr" size="mini" type="primary">请选择收货地址+</button>
methods: {
async chooseAddr () {
const res = await uni.chooseAddress()
console.log(res)
}
}
-
如果点击了取消,或者没有正确选到地址,下标0有值,如果点击了确定(拿到了地址),返回值下标1有值,下标0为null
购物车 - 地址信息用vuex管理起来并缓存
- 根据上面的步骤,已经可以拿到地址了,但是地址可能多个地方要用,所以用vuex存起来
- 来到
store/modules
新建user.js
,这个模块以后专门存当前用户的个人信息
export default {
namespaced: true,
state() {
return {
address: {}
}
},
getters: {
// 专门用来拼接地址的
addressStr(state) {
return state.address.provinceName + state.address.cityName + state.address.countyName + state.address
.detailInfo
}
},
mutations: {
setAddress(state, addr) {
state.address = addr
}
}
}
- 来到
store/index
做导入和注册
import user from './modules/user.js'
modules: {
cart,
user
}
购物车 - 地址渲染出来
- 首先回到
my-address
,在按钮的点击事件里把拿到的地址存到vuex里
async chooseAddr() {
// 要选择微信上的地址
const res = await uni.chooseAddress()
if (res[0]) {
uni.showToast({
title: '没选择任何地址',
icon: 'error'
})
} else {
// 有地址,拿到地址存起来
this.$store.commit('user/setAddress', res[1])
}
}
- 再导入getters里的内容,做渲染
import {
mapGetters
} from 'vuex'
export default {
name: "my-address",
computed: {
...mapGetters('user', ['addressStr'])
},
- 渲染时要有选择的显示按钮或地址
<view v-if="!addressStr" class="btn-box">
<button @click="chooseAddr" size="mini" type="primary">请选择收货地址+</button>
</view>
<!-- 放选择后的地址信息 -->
<view v-else class="">
<view class="addr">收货地址:{{ addressStr }}</view>
</view>
购物车-完整显示地址
<view v-else style="padding:10px">
<view style="display: flex;justify-content: space-between;">
<view class="name">
收货人:{{ address.userName }}
</view>
<view class="call">
电话: {{ address.telNumber }}
</view>
</view>
<view class="addr">收货地址:{{ addressStr }}</view>
</view>
- 这里要访问address,这个数据在vuex里,所以做一个映射
import {
mapGetters,
mapState
} from 'vuex'
computed: {
...mapGetters('user', ['addressStr']),
...mapState('user', ['address'])
},
购物车 - 结算区域组件准备
- 来到
components
右键新建组件,专门放结算区域,名字叫my-settle
购物车 - 结算区域基本布局
- 结构如下
<view class="settle-container">
<label class="radio">
<radio color="#c00000" value="" /><text>全选</text>
</label>
<view class="totalprice-box">
合计:<text style="color:#c00000">¥ 665.00</text>
</view>
<view class="order-btn">结算(0)</view>
</view>
- 样式如下:
- 大盒子要固定定位、里面内容弹性布局
.settle-container {
height: 50px;
background-color: #fff;
position: fixed;
border-top: 1rpx solid #dddddd;
left: 0;
bottom: 0;
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
.order-btn {
width: 200rpx;
height: 80rpx;
line-height: 80rpx;
background-color: #c00000;
color:#fff;
text-align: center;
}
}
- 来到
cart
页面使用它,使用后发现,购物车里有一部分内容被盖住了,所以需要给购物车的大盒子加padding
<view style="padding-bottom:50px;">
...................
<my-settle></my-settle>
</view>
购物车 - 渲染结算区域已勾选总数量
- 总数量因为之前已经统计过,所以可以来到
my-settle
直接用
import {
mapGetters
} from 'vuex'
export default {
name: "my-settle",
computed: {
...mapGetters('cart', ['total'])
}
}
- 然后渲染到购物车数量上
<view class="order-btn">结算({{ total }})</view>
购物车 - 渲染已勾选商品总价
- 总价因为之前没有,所以还得回来到
store/modules/cart
里写一个getter
用来计算总价
// 统计总价
totalPrice(state) {
// 统计出总数量--要统计出选中的数量
let price = 0
state.cartList.forEach(v => {
if (v.goods_checked) {
price += v.goods_count * v.goods_price
}
})
// 把统计的数量返回出去
return price
},
- 然后来到
my-settle
进行使用
import {
mapGetters
} from 'vuex'
export default {
name: "my-settle",
computed: {
...mapGetters('cart', ['total', 'totalPrice'])
},
}
<view class="totalprice-box">
合计:<text style="color:#c00000">¥ {{ totalPrice }}</text>
</view>
购物车 - 动态渲染全选按钮的选中状态
- 因为全选的选中状态依赖了购物车里的数据,而购物车里的数据在vuex里,所以来到vuex里写一个计算属性
checkAll(state) {
// 统计每个商品的goods_checked是否为true,都为true才返回true,有一个是false就返回false,所以可以用every
return state.cartList.every(v => v.goods_checked)
}
- 然后来到
my-settle
进行调用
import {
mapGetters
} from 'vuex'
export default {
name: "my-settle",
computed: {
...mapGetters('cart', ['total', 'totalPrice', 'checkAll'])
},
}
- 绑定到单选框
<radio :checked="checkAll" color="#c00000" value="" /><text>全选</text>
购物车 - 实现全选/全不选功能
- 因为要全选/全不选,需要改动到vuex里的商品数据,所以需要提供一个mutations用来修改
setAllGoodsChekced(state, checked) {
state.cartList.forEach(v => v.goods_checked = checked)
}
- 这个功能需要给单选框加点击事件,点击时传递当前选中状态取反即可
<radio @click="clickAll" :checked="checkAll" color="#c00000" value="" /><text>全选</text>
clickAll() {
// 全选原本是true就给false,原本是false就全部给true
// 但是因为改的是vuex里的数据,所以又得封装方法
this.$store.commit('cart/setAllGoodsChekced', !this.checkAll)
}
购物车 - 渲染购物车为空时的结构
- 无非就是再提供一个整体的view,判断有购物车数据就渲染之前购物车页面,没有就显示空空如也的view
<view v-if="cartList.length" style="padding-bottom:50px;">...........</view>
<view v-else class="empty-box">
<image src="../../static/[email protected]"></image>
<text>空空如也~</text>
</view>
- 样式如下
.empty-box {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
image {
width: 80px;
height: 80px;
}
}
购物车 - 分支合并
- 将
cart
分支进行本地提交:
git add .
git commit -m "完成了购物车的开发"
- 将本地的
cart
分支推送到码云:
git push -u origin cart
- 将本地
cart
分支中的代码合并到master
分支:
git checkout master
git merge cart
git push
- 删除本地的
cart
分支:
git branch -d cart
<view class="settle-container">
<label class="radio">
<radio color="#c00000" value="" /><text>全选</text>
</label>
<view class="totalprice-box">
合计:<text style="color:#c00000">¥ 665.00</text>
</view>
<view class="order-btn">结算(0)</view>
</view>
标签:goods,state,cart,购物车,获取,地址,vuex,id
From: https://www.cnblogs.com/strundent/p/17078373.html