场景简述:
1、没有购物车接口,购物车数据存储在本地缓存中,名为 carts ;
2、carts中的商品信息没有图片,所以没渲染图片;
3、使用了Vant3组件;
4、没有使用eslint格式化;
5、<script setup></script>
使用到的Vant3组件:
app.use(Icon);
app.use(NavBar);
app.use(Button);
app.use(SubmitBar);
app.use(Checkbox);
app.use(CheckboxGroup);
app.use(Empty);
app.use(Stepper);
0、简洁版:
效果图:

单选+数量增减+金额计算

全选+删除单个商品

空状态
完整代码:
<template>
<div class="cart">
<!-- 头部 -->
<div class="header" >
<a href="javascript:history.go(-1)"><van-icon name="arrow-left" class="arrow-left"/></a>
<van-nav-bar title="购物车" fixed/>
</div>
<!-- 购物车start -->
<div class="cart_container">
<!-- vant空状态 -->
<van-empty v-show="isShow" description="购物车目前还没有商品" image="https://img.yzcdn.cn/vant/custom-empty-image.png">
<router-link to="/goods">
<van-button round type="danger" class="bottom-button">
去购物
</van-button>
</router-link>
</van-empty>
<!-- 空状态end -->
<!-- 购物车start -->
<div v-for="(item,index) in store" :key="index" class="list">
<van-swipe-cell>
<!-- 选框 -->
<div class="checkbox">
<van-checkbox
:name="item"
v-model="item.checked"
@change=onChecked
checked-color="#f11a27"
></van-checkbox>
</div>
<!-- 商品信息 -->
<div class="goods_info">
<div class="title">{{item.title}}</div>
<div class="bottom">
<div class="price">
<span>¥</span>
{{item.sell_price}}
</div>
<van-stepper v-model="item.num" theme="round" button-size="22" disable-input @change="onNum()" />
</div>
</div>
<!-- 左滑删除 -->
<template #right>
<van-icon name="delete" class="delete-button" @click="onDelete(item)"/>
</template>
</van-swipe-cell>
</div>
<!-- 购物车end -->
</div>
<!-- 购物车end -->
<!-- 结算start -->
<van-submit-bar
:price="total*100"
button-text="提交订单"
@submit="onSubmit"
class="settlement">
<van-checkbox
v-model="allChecked"
checked-color="#f11a27"
@click="onAll">
全选
</van-checkbox>
</van-submit-bar>
<!-- 结算end -->
</div>
</template>
<script setup>
import { reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
const store = reactive([])
// 空状态
const isShow = ref(false)
const emptyStatus =()=>{ store.length==0 ? isShow.value=true : isShow.value=false }
// 进入购物车通过本地缓存中购物车里的数据渲染页面,并给每一条数据添加checked属性
const getCarts = () => {
// 清空数据
store.length = 0
let arr = JSON.parse(localStorage.getItem('carts'))
if(arr){
arr.forEach(el=>{
el.checked = false
})
store.push(...arr)
localStorage.setItem('carts',JSON.stringify(store))
}
emptyStatus()
}
getCarts()
// 全选控制单选 ref取值用value 赋值也是用value
const allChecked = ref(false)
const onAll=()=>{
store.forEach(el=>{
el.checked = allChecked.value
})
localStorage.setItem('carts',JSON.stringify(store))
getTotal()
}
// 单选控制全选
const onChecked=()=>{
// 商品都被选中时,全选按钮高亮
allChecked.value = store.every(el=>{
return el.checked
})
localStorage.setItem('carts',JSON.stringify(store))
getTotal()
}
// 用户增减商品数量,更新本地存储数据
const onNum=(quantity)=>{
localStorage.carts = JSON.stringify(store)
getTotal()
}
// 金额计算
let total = ref(0)
const getTotal=()=>{
// 所有被选项组成数组返回
let userCheck=store.filter(val => {
return val.checked == true
});
total.value=0
userCheck.map(el=>{
total.value += el.sell_price*el.num
})
}
// 删除商品
const onDelete=(item)=>{
store.length = 0
let arr = JSON.parse(localStorage.getItem('carts'))
let id = item.id
let deleteArr = arr.filter(el=>{
return el.id != id
})
store.push(...deleteArr)
if(store.length == 0){
allChecked.value = false
emptyStatus()
}
localStorage.setItem('carts',JSON.stringify(deleteArr))
getTotal()
}
// 提交
const router = useRouter()
const onSubmit=()=>{
router.push('/submit')
}
</script>
<style lang="less" scoped>
.cart{
margin: 0.3rem;
padding: 0.05rem 0 3rem 0;
.header{
.manage{
z-index: 999;
color: #fff;
position: fixed;
top: 0.27rem;
right: 0.6rem;
font-size: 0.45rem;
font-weight: 550;
}
}
.cart_container{
margin-top: 1.22rem;
.list{
position: relative;
height: 3rem;
border-radius: 20px;
box-shadow: 0px 0px 5px #ccc;
margin-bottom: 20px;
display: flex;
justify-content: space-around;
align-items: center;
.checkbox{
position: absolute;
top: 0.7rem;
left: 0.1rem;
}
.goods_info {
width: 7.8rem;
height: 2rem;
display: flex;
justify-content: space-around;
flex-direction: column;
margin-left: 1rem;
.title{
font-size: 0.4rem;
}
.bottom{
display: flex;
justify-content: space-between;
align-items: center;
.price{
color: #c82519;
font-size: 0.45rem;
}
}
}
.delete-button {
display: block;
color: #c82519;
margin-top: 0.7rem;
margin-right: 0.1rem;
}
}
.bottom-button {
width: 3rem;
height: 1rem;
}
}
.settlement{
margin-bottom: -0.1rem;
}
}
/deep/.van-submit-bar{
bottom:1.4rem;
}
/deep/.van-swipe-cell{
width: 100%;
}
</style>
分模块解析:
1、单选全选
使用到了:数组方法——forEach() ,every() ,以及解构赋值(...)
(1)单选逻辑——选中购物车中当前商品
0、总体逻辑:为本地缓存中的数据添加checked属性
1、复选框根据:name="item"进行渲染;
2、使用 v-model 进行双向数据绑定。商品的checked属性为true时,选框选中;
3、定义全局变量store,存放本地缓存中的数据,store应为响应式;
4、定义getCarts函数
(1)将本地缓存中的数据放到数组arr中,注意要JSON.parse();
(2)如果本地缓存的购物车里有数据,即arr中有值——使用forEach()对arr进行遍历,为每一件商品添加checked属性,初始值为false(未选中);
(3)解构arr,由数组变成一个一个的对象,并push到store容器中;
(4)更新本地缓存
5、调用空状态函数emptyStatus(),如果本地缓存的购物车里没有数据,就显示空状态;
6、在setup中调用getCarts(),使checked属性得到渲染
HTML结构:
<!-- 单选选框 -->
<div v-for="(item,index) in store" :key="index" class="list">
<div class="checkbox">
<van-checkbox
:name="item"
v-model="item.checked"
@change=onChecked
checked-color="#f11a27"
></van-checkbox>
</div>
</div>
JS代码:
// 单选按钮
const store = reactive([])
// 进入购物车时,通过本地缓存中购物车里的数据渲染页面,并给每一条数据添加checked属性
const getCarts = () => {
// 清空store
store.length = 0
let arr = JSON.parse(localStorage.getItem('carts'))
if(arr){
arr.forEach(el=>{
el.checked = false
})
store.push(...arr)
localStorage.setItem('carts',JSON.stringify(store))
}
emptyStatus()
}
getCarts()
(2)全选逻辑--全选控制单选,选中购物车中所有内容
0、总体逻辑:使全选的值与每一个单选的值相同
1、在全选选框中添加 @click="onAll" 点击事件;
2、使用 v-model 进行双向数据绑定。allChecked初始值为 false ,使用了ref,后续对allChecked赋值的时候,需要用到allChecked.value;
3、在onAll()函数中,使用forEach()遍历store中的商品数据,给每一件商品的 checked属性赋值——当allChecked.value = true时,每一件商品的checked属性也为true;反之,全选选框未勾选,即allChecked.value = false时,每一件商品的checked属性也为false;
4、调用金额结算函数。
HTML结构:
<!-- 结算start -->
<van-submit-bar
:price="total*100"
button-text="提交订单"
@submit="onSubmit"
class="settlement">
<van-checkbox
checked-color="#f11a27"
v-model="allChecked"
@click="onAll">
全选
</van-checkbox>
</van-submit-bar>
<!-- 结算end -->
JS代码:
// 全选控制单选 ref取值用value 赋值也是用value
const allChecked = ref(false)
const onAll=()=>{
store.forEach(el=>{
el.checked = allChecked.value
})
localStorage.setItem('carts',JSON.stringify(store))
getTotal()
}
(3)单选控制全选——购物车中所有商品均被选中,全选按钮也被选中
0、总体逻辑:用every()遍历本地缓存中的数据,当所有的checked属性均为true时,全选也为true
1、在单选选框中添加 @change=onChecked 事件;
2、使用every()遍历store,返回值是boolean,如果每个商品都被选中,every返回true,有一个商品没有被选中,则返回false,将其赋值给allChecked.value(控制全选是否高亮的值);
4、调用金额结算函数。
HTML结构:
<!-- 单选选框 -->
<div v-for="(item,index) in store" :key="index" class="list">
<div class="checkbox">
<van-checkbox
:name="item"
v-model="item.checked"
@change=onChecked
checked-color="#f11a27"
></van-checkbox>
</div>
</div>
<!-- 结算start -->
<van-submit-bar
:price="total*100"
button-text="提交订单"
@submit="onSubmit"
class="settlement">
<!-- 全选选框 -->
<van-checkbox
v-model="allChecked"
checked-color="#f11a27"
@click="onAll">
全选
</van-checkbox>
</van-submit-bar>
<!-- 结算end -->
JS代码:
// 单选控制全选
const onChecked=()=>{
// 商品都被选中时,全选按钮高亮
allChecked.value = store.every(el=>{
return el.checked
})
getTotal()
}
2、数量加减——改变数量,同时商品金额动态改变
0、总体逻辑:本质是商品中 num 值的改变和简单的数学运算;
1、用户增减商品数量,更新本地存储数据;
2、调用getTotal()计算金额
HTML结构:
<div v-for="(item,index) in store" :key="index" class="list">
<van-stepper v-model="item.num" @change="onNum"/>
</div>
JS代码:
// 用户增减商品数量,更新本地存储数据
const onNum=()=>{
localStorage.carts = JSON.stringify(store)
getTotal()
}
3、金额计算
0、总体逻辑:过滤出被选中的商品,清空总价,计算总价(+=)
1、由变量total的值来确定商品价格price;
2、由于组件中存在固定单位,商品价格应该*100;
3、filter()过滤出用户选择的商品,返回数组;
4、价格先清零,再进行计算,否则将保留原先计算出的价格;
5、使用map()对数组中每一个商品的价格进行计算,注意要使用 += 赋值给total.value;
6、在单选全选,数量计算中调用价格计算的函数,确保每次操作都重新计算价格
HTML结构:
<!-- 结算start -->
<van-submit-bar
:price="total*100"
button-text="提交订单"
@submit="onSubmit"
class="settlement"
>
</van-submit-bar>
<!-- 结算end -->
JS代码:
// 金额计算
let total = ref(0)
const getTotal=()=>{
// 所有被选项组成数组返回
let userCheck=store.filter(val => {
return val.checked == true
});
total.value=0
userCheck.map(el=>{
total.value += el.sell_price*el.num
})
}
4、空状态
购物车内没有商品的时候显示,主要在 进入购物车 和删除购物车内所有的商品 时调用
HTML结构:
<!-- vant空状态 -->
<van-empty v-show="isShow" description="购物车目前还没有商品" image="https://img.yzcdn.cn/vant/custom-empty-image.png">
<router-link to="/goods">
<van-button round type="danger" class="bottom-button">
去购物
</van-button>
</router-link>
</van-empty>
<!-- 空状态end -->
JS代码:
// 空状态
const isShow = ref(false)
const emptyStatus =()=>{ store.length==0 ? isShow.value=true : isShow.value=false }
5、左滑删除
0、总体逻辑:(1)把数据从缓存拿出来;(2)过滤删除数据;(3)存储最新数据
1、清空store容器中的商品数据;
2、过滤已经删除的商品数据,将没有删除的商品数据,组成数组返回;
3、将现存的商品数据push到store容器中;
4、如果用户把商品数据都删了,全选按钮要取消勾选,同时显示空状态;
5、结算
HTML结构:
<!-- 左滑删除 -->
<template #right>
<van-icon name="delete" class="delete-button" @click="onDelete(item)"/>
</template>
JS代码:
// 删除商品
const onDelete=(item)=>{
store.length = 0
let arr = JSON.parse(localStorage.getItem('carts'))
let id = item.id
let deleteArr = arr.filter(el=>{
return el.id != id
})
store.push(...deleteArr)
if(store.length == 0){
allChecked.value = false
emptyStatus()
}
localStorage.setItem('carts',JSON.stringify(deleteArr))
getTotal()
}
6、结算跳转
路由跳转
HTML结构:
<!-- 结算start -->
<van-submit-bar
:price="total*100"
button-text="提交订单"
@submit="onSubmit"
class="settlement"
>
</van-submit-bar>
<!-- 结算end -->
JS代码:
// 提交结算
const router = useRouter()
const onSubmit=()=>{
router.push('/submit')
}