思路
- 向购物车组件
ShoppingCart.vue传入商品列表数组lsit,商品列表数组中每个对象包含num属性,表示购买该商品的数量,在data中将list深拷贝给shoppingList【不能直接修改props】。
- 通过计算属性返回购买了的商品列表
selectList,这里通过判断商品列表中的num是否为0筛选出selectList
- 通过计算属性返回购买的商品总数
num,这里通过遍历shoppingListreduce累计返回num
- 通过计算属性返回购买商品的总价
totalPrice,这里通过遍历shoppingListreduce累计计算返回
- 通过 watch 监听父组件传过来的商品
list的变化,一旦发生变化就将新的list深拷贝给shoppingList
- 当修改了购物车组件的
shoppingList之后通过触发自定义事件this.$emit("changeList",this.shoppingList)将修改后的商品列表回传给父组件,父组件接收并更新商品列表phoneList
代码
购物车子组件 ShoppingCart.vue
<template>
<div>
<div
class="shop-bottom-wrapper"
:class="{
'shopping-cart-top-display-right-animation':
displayStyle && isShoppingCartTopDisplay,
'shopping-cart-top-display-top-animation':
!displayStyle && isShoppingCartTopDisplay,
}"
>
<p class="top-description" v-if="isShoppingCartTopDisplay">
可下单金额¥935,158.83
</p>
<div
class="shop-wrapper"
:class="
shoppingCartAnimation
? 'shopping-cart-expansion-animation'
: 'shopping-cart-shrink-animation'
"
>
<div class="shop-img-wrapper" @click="shoppingDetailInfo()">
<img :src="require('@/assets/icon/shop.png')" class="shop-img" />
<div class="shop-num" v-if="num !== 0">{{ num }}</div>
</div>
<div class="shop-text-wrapper" v-if="num !== 0">
<div class="price-wrapper">
<span class="description">
合计:¥ <span class="total-price">{{ totalPrice }}</span>
</span>
</div>
<p class="description">优惠券抵扣:¥0.5</p>
</div>
<div class="button" v-if="num !== 0">提交订单</div>
</div>
</div>
<div class="mask" v-if="dialog" @click.self="hideMask()"></div>
<div class="dialog" :class="dialog ? 'dialog-display-animation' : ''">
<p class="dialog-top-description">可下单金额¥935,158.83</p>
<div class="clear-box" @click="clearShoppingCart()">
<img :src="require('@/assets/image/dustbin.png')" />
<span>清空商品</span>
</div>
<div class="shop-list">
<div class="shop-item" v-for="item in selectList" :key="item.id">
<img :src="item.imgSrc" />
<div class="shop-text-wrapper">
<h3>{{ item.title }}</h3>
<p>¥ {{ item.price }}</p>
</div>
<div class="shop-button-box">
<span @click="reduceNum(item.id)">-</span>
<span>{{ item.num }}</span>
<span @click="addNum(item.id)">+</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "ShoppingCart",
props: {
list: {
type: Array,
required: true,
default() {
return [];
},
},
},
data() {
return {
shoppingList: JSON.parse(JSON.stringify(this.list)),
displayStyle: true,
shoppingCartAnimation: false,
dialog: false,
};
},
computed: {
selectList() {
return this.shoppingList.filter((item) => item.num !== 0);
},
num() {
return this.shoppingList.reduce((total, item) => {
return (total = total + item.num);
}, 0);
},
totalPrice() {
return this.shoppingList.reduce((total, item) => {
return (total = total + item.num * parseFloat(item.price));
}, 0);
},
isShoppingCartTopDisplay() {
return this.shoppingCartAnimation && !this.dialog;
},
},
watch: {
num(n) {
if (n > 0) {
this.shoppingCartAnimation = true;
} else {
this.shoppingCartAnimation = false;
this.displayStyle = true;
this.dialog = false;
}
},
list: {
handler(n) {
this.shoppingList = JSON.parse(JSON.stringify(n));
},
deep: true,
},
},
methods: {
shoppingDetailInfo() {
this.dialog = true;
},
hideMask() {
this.dialog = false;
this.displayStyle = false;
},
clearShoppingCart() {
this.shoppingList.forEach((item) => {
item.num = 0;
});
this.$emit("changeList", this.shoppingList);
this.dialog = false;
},
reduceNum(id) {
this.shoppingList.forEach((item) => {
if (item.id === id && item.num !== 0) {
item.num--;
}
});
this.$emit("changeList", this.shoppingList);
},
addNum(id) {
this.shoppingList.forEach((item) => {
if (item.id === id) {
item.num++;
}
});
this.$emit("changeList", this.shoppingList);
},
},
};
</script>
<style lang="stylus" scoped>
@keyframes shoppingCartTopDisplayRight
from
width 50px;
to
width 94%;
@keyframes shoppingCartTopDisplayTop
from
height 0;
to
height 75px;
.shop-bottom-wrapper
position fixed;
left 3%;
bottom 80px;
z-index 999;
width 94%;
height 75px;
border-radius 25px;
.top-description
padding 2px 0 0 45px;
font-size 12px;
overflow hidden;
white-space nowrap;
.shop-wrapper
position absolute;
bottom 0;
display flex;
flex-direction row;
align-items center;
width 50px;
height 50px;
color white;
border-radius 25px;
background rgb(64, 63, 63);
.shop-img-wrapper
position relative;
display flex;
justify-content center;
align-items center;
width 50px;
height 50px;
.shop-img
width 20px;
height 20px;
.shop-num
position absolute;
top 8px;
right 5px;
width 15px;
height 15px;
font-size 12px;
line-height 15px;
text-align center;
background red;
border-radius 50%;
.shop-text-wrapper
flex 1;
padding-left 8px;
overflow hidden;
white-space nowrap;
.description
font-size 12px;
.total-price
font-size 16px;
.button
display flex;
justify-content center;
align-items center;
width 120px;
height 100%;
background rgb(40, 196, 40);
border-top-right-radius 25px;
border-bottom-right-radius 25px;
overflow hidden;
white-space nowrap;
.shopping-cart-expansion-animation
width 100%;
transition width 0.5s;
.shopping-cart-shrink-animation
width 50px;
transition width 0.5s;
.shopping-cart-top-display-right-animation
background #fff2d7;
animation shoppingCartTopDisplayRight 0.5s;
animation-fill-mode forwards;
.shopping-cart-top-display-top-animation
background #fff2d7;
animation shoppingCartTopDisplayTop 0.8s;
animation-fill-mode forwards;
.dialog
position fixed;
bottom 70px;
width 100%;
height 0;
border-top-left-radius 5px;
border-top-right-radius 5px;
background white;
.dialog-top-description
padding 5px 0;
font-size 12px;
text-align center;
background #fff2d7;
border-top-left-radius 10px;
border-top-right-radius 10px;
.clear-box
padding 10px;
font-size 12px;
text-align right;
color #DEDEDE;
img
width 15px;
vertical-align middle;
span
padding-left 5px;
vertical-align middle;
.shop-list
padding-bottom 50px;
height 200px;
overflow auto;
.shop-item
padding 10px;
display flex;
flex-direction row;
align-items center;
img
width 35px;
height 50px;
.shop-text-wrapper
flex 1;
padding 0 10px;
p
color red;
.shop-button-box
width 80px;
height 30px;
border 1px solid #DEDEDE;
border-radius 5px;
span
display inline-block;
width 30%;
height 100%;
line-height 30px;
text-align center;
&:nth-child(2)
width 40%;
border-left 1px solid #DEDEDE;
border-right 1px solid #DEDEDE;
.dialog-display-animation
height 300px;
transition height 0.8s;
.mask
position fixed;
top 0;
left 0;
width 100%;
height 100%;
background-color rgba(0, 0, 0, 0.5);
</style>
父组件 index.vue
<template>
<div>
<div class="phone-list">
<div class="item-wrapper" v-for="(item, index) in phoneList" :key="index">
<img :src="item.imgSrc" class="item-img" />
<div class="text-wrapper">
<h3>{{ item.title }}</h3>
<p>¥ {{ item.price }}</p>
</div>
<div class="button-box" @click="addGoods(item.id)">
+
<div class="num" v-if="item.num !== 0">{{ item.num }}</div>
</div>
</div>
</div>
<shopping-cart
:list="phoneList"
@changeList="getNewPhoneList"
></shopping-cart>
</div>
</template>
<script>
import ShoppingCart from "./components/ShoppingCart.vue";
export default {
name: "shoppingBar",
components: { ShoppingCart },
data() {
return {
phoneList: [
{
id: "1",
imgSrc: require("@/assets/image/phone1.jpg"),
title: "OPPO Free系列 003",
price: "1999",
num: 0,
},
{
id: "2",
imgSrc: require("@/assets/image/phone2.jpg"),
title: "OPPO Free系列 003",
price: "2999",
num: 0,
},
{
id: "3",
imgSrc: require("@/assets/image/phone3.jpg"),
title: "OPPO Free系列 003",
price: "3999",
num: 0,
},
{
id: "4",
imgSrc: require("@/assets/image/phone3.jpg"),
title: "OPPO Free系列 003",
price: "4999",
num: 0,
},
{
id: "5",
imgSrc: require("@/assets/image/phone3.jpg"),
title: "OPPO Free系列 003",
price: "5999",
num: 0,
},
{
id: "6",
imgSrc: require("@/assets/image/phone3.jpg"),
title: "OPPO Free系列 003",
price: "6999",
num: 0,
},
{
id: "7",
imgSrc: require("@/assets/image/phone3.jpg"),
title: "OPPO Free系列 003",
price: "7999",
num: 0,
},
],
};
},
methods: {
addGoods(id) {
this.phoneList.forEach((item) => {
if (item.id === id) {
item.num++;
}
});
},
getNewPhoneList(data) {
this.phoneList = JSON.parse(JSON.stringify(data));
},
},
};
</script>
<style lang="stylus" scoped>
.phone-list
padding-bottom 70px;
.item-wrapper
position relative;
padding 20px;
display flex;
flex-direction row;
align-items center;
background rgb(245, 244, 244);
.item-img
width 50px;
height 50px;
.text-wrapper
padding 0 15px;
flex 1;
p
color red;
.button-box
position relative;
width 25px;
height 25px;
display flex;
justify-content center;
align-items center;
color white;
border-radius 5px;
background rgb(40, 196, 40);
.num
position absolute;
top -9px;
right -9px;
display flex;
justify-content center;
align-items center;
width 18px;
height 18px;
font-size 12px;
background red;
border-radius 5px;
</style>
小结
样式选择
1、动态选择多个class
:class="{ '类名1':变量, '类名2':变量}"
2、动态选择class【2选一】
:class="变量? '类名1': '类名2'"