1.在商品详细页的添加到购物车按钮内添加触发事件
2.在商品详情页接收这个事件
3.在状态管理内进行数据处理
3.1 index.js导入action.js和getters.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import actions from './actions'
Vue.use(Vuex)
const state = {
cartList: []
}
const store = new Vuex.Store({
state,
// mutations,
getters,
actions
})
export default store
3.2 在actions内处理数据和异步处理
export default {
addCart(context, payload) {
//进行异步操作
return new Promise(((resolve, reject) => {
//定义变量oldProduct,在cartList数组内根据item查找是否item.iid和payload.iid相同
let oldProduct = context.state.cartList.find(item => item.iid === payload.iid)
if (oldProduct) {
//如果有则定义下标为state内类型为oldProduct的cartList
//然后该下标的数量加1
//并且弹出'商品新添加一件'
const index = context.state.cartList.indexOf(oldProduct)
context.state.cartList[index].count++
resolve('商品新添加一件')
} else {
//如果没有,则给payload的数量赋值为1
//然后购物车内补选中
//并且把payload的内容加到carList内
//最后弹出'商品添加成功'
payload.count = 1
payload.checked = false
context.state.cartList.push(payload)
resolve('商品添加成功')
}
}))
}
}
3.3 在getters内把对象转成计算熟悉
const getters = {
cartLength(state) {
//在state内返回购物车的长度
return state.cartList.length
},
cartList(state) {
//返回在state内购物车的数据
return state.cartList
}
}
export default getters
4.添加成功弹出窗口提醒
4.1配置$toast
toast.vue
<template>
<div v-show="isShow">
<div class="toast">
{{message}}
</div>
</div>
</template>
<script>
export default {
name: "Toast",
data() {
return {
message: '',
isShow: false
}
},
methods: {
show(message, duration=1000) {
this.isShow = true
this.message = message
setTimeout(() => {
this.isShow = false
this.message = ''
}, duration)
}
}
}
</script>
<style scoped>
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 5px;
background-color: rgba(0, 0, 0, .75);
color: #fff;
padding: 8px 10px;
z-index: 999;
}
</style>
index.js
import Toast from './Toast'
let obj = {}
obj.install = function (Vue) {
// 1. 创建组件构造器
const toastConstuctor = Vue.extend(Toast)
// 2. new的方式,根据组件构造器,可以创建出来一个组件对象
const toast = new toastConstuctor()
// 3. 将组件对象,手动挂载到某一个元素上
toast.$mount(document.createElement('div'))
// 4. toast.$el 对应的就是div
document.body.appendChild(toast.$el)
Vue.prototype.$toast = toast
}
export default obj
4.2 从main.js内导入
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from "./store";
import toast from './components/common/toast'
Vue.config.productionTip = false
Vue.prototype.$bus = new Vue()
Vue.use(toast)
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
4.4 在处理完窗口后运用
4.添加数据到购物车
5.购物车数据加载
<template>
<div id="shop-item">
<div class="item-selector">
<CheckButton :is-checked="itemInfo.checked" @click.native="checkClick" />
</div>
<div class="item-img">
<img :src="itemInfo.imgURL" alt="商品图片" @load="imgLoad">
</div>
<div class="item-info">
<div class="item-title">{{itemInfo.title}}</div>
<div class="item-desc">{{itemInfo.desc}}</div>
<div class="info-bottom">
<div class="item-price left">¥{{itemInfo.price}}</div>
<div class="item-count right">x{{itemInfo.count}}</div>
</div>
</div>
</div>
</template>
<script>
import CheckButton from './CheckButton'
export default {
name: "ShopCartItem",
props: {
//商品对象
itemInfo: Object
},
components: {
CheckButton
},
methods: {
checkClick: function () {
//响应是否有选中itemInfo,并且改变checke值使CheckButton的样式改变
this.itemInfo.checked = !this.itemInfo.checked;
},
imgLoad() {
// 判断, 所有的图片都加载完了, 那么进行一次回调就可以了.
if (++this.counter === this.imagesLength) {
this.$emit('imageLoad');
}
}
}
}
</script>
<style scoped>
#shop-item {
width: 100%;
display: flex;
font-size: 0;
padding: 5px;
border-bottom: 1px solid #ccc;
}
.item-selector {
width: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.item-title, .item-desc {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.item-img {
padding: 5px;
/*border: 1px solid #ccc;*/
}
.item-img img {
width: 80px;
height: 100px;
display: block;
border-radius: 5px;
}
.item-info {
font-size: 17px;
color: #333;
padding: 5px 10px;
position: relative;
overflow: hidden;
}
.item-info .item-desc {
font-size: 14px;
color: #666;
margin-top: 15px;
}
.info-bottom {
margin-top: 10px;
position: absolute;
bottom: 10px;
left: 10px;
right: 10px;
}
.info-bottom .item-price {
color: orangered;
}
</style>
6.购物车选择和全选按键
按钮代码:
<template>
<div>
<div class="icon-selector" :class="{'check': isChecked}" @click="selectItem">
<img src="../../../assets/img/cart/tick.svg" alt="">
</div>
</div>
</template>
<script>
export default {
name: "CheckButton",
props: {
isChecked: {
type: Boolean,
default: false
}
},
data: function () {
return {
//checked等于这个dom变量
checked: this.value
}
},
methods: {
selectItem: function () {
//发送事件
this.$emit('checkBtnClick')
}
},
watch: {
//监听value是否有改变,如果改变则checked的值特相应改变
value: function (newValue) {
this.checked = newValue;
}
}
}
</script>
<style scoped>
.icon-selector {
position: relative;
margin: 0;
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #aaa;
cursor: pointer;
}
.check {
background-color: #ff8198;
border-color: #ff8198;
}
</style>
全选按钮代码:
<template>
<div class="bottom-menu">
<CheckButton class="select-all"
:is-checked="isSelectorAll"
@click.native="checkClick">
</CheckButton>
<span>全选</span>
<span class="total-price">合计: ¥{{totalPrice}}</span>
<span class="buy-product" @click="calcClick">去结算({{cartLength}})</span>
</div>
</template>
<script>
import CheckButton from './CheckButton'
import { mapGetters } from 'vuex'
export default {
name: "BottomBar",
components: {
CheckButton
},
computed: {
...mapGetters([
'cartList',
'cartLength'
]),
totalPrice() {
//计算总和
//用filter来判断item.checked的布尔值,true存入cartList
//用reduce函数对数组中的所有内容进行数量乘上价格记总,并保留两位小数
const cartList = this.cartList;
return cartList.filter(item => {
return item.checked
}).reduce((preValue, item) => {
return preValue + item.count * item.price
}, 0).toFixed(2)
},
checkLength() {
//监听组件
//返回Vuex内getters内的cartList查找item中的checked属性,和长度
return this.$store.getters.cartList.filter(item => item.checked).length
},
isSelectorAll() {
//判断是否全部选中
if (this.$store.getters.cartList.length === 0) return false
return !this.$store.getters.cartList.find(item => !item.checked)
}
},
methods: {
checkClick() {
//如果全部选中则全部取消选择,如果没有则部分或全部选中
if(this.isSelectorAll) {
this.$store.getters.cartList.forEach(item => item.checked = false)
} else {
this.$store.getters.cartList.forEach(item => item.checked = true)
}
},
calcClick() {
//如果没有全部选中则弹出'请选择购买的商品'
if(!this.isSelectorAll) {
this.$toast.show('请选择购买的商品')
}
}
}
}
</script>
<style scoped>
.bottom-menu {
width: 100%;
height: 44px;
background-color: #eee;
position: fixed;
bottom: 49px;
left: 0;
box-shadow: 0 -2px 3px rgba(0, 0, 0, .2);
font-size: 14px;
color: #888;
line-height: 44px;
padding-left: 35px;
box-sizing: border-box;
}
.bottom-menu .select-all {
position: absolute;
line-height: 0;
left: 12px;
top: 13px;
}
.bottom-menu .total-price {
margin-left: 15px;
font-size: 16px;
color: #666;
}
.bottom-menu .buy-product {
background-color: orangered;
color: #fff;
width: 100px;
height: 44px;
text-align: center;
line-height: 44px;
float: right;
}
</style>
选择按钮代码:
<template>
<div id="shop-item">
<div class="item-selector">
<CheckButton :is-checked="itemInfo.checked" @click.native="checkClick" />
</div>
<div class="item-img">
<img :src="itemInfo.imgURL" alt="商品图片" @load="imgLoad">
</div>
<div class="item-info">
<div class="item-title">{{itemInfo.title}}</div>
<div class="item-desc">{{itemInfo.desc}}</div>
<div class="info-bottom">
<div class="item-price left">¥{{itemInfo.price}}</div>
<div class="item-count right">x{{itemInfo.count}}</div>
</div>
</div>
</div>
</template>
<script>
import CheckButton from './CheckButton'
export default {
name: "ShopCartItem",
props: {
//商品对象
itemInfo: Object
},
components: {
CheckButton
},
methods: {
checkClick: function () {
//响应是否有选中itemInfo,并且改变checke值使CheckButton的样式改变
this.itemInfo.checked = !this.itemInfo.checked;
},
imgLoad() {
// 判断, 所有的图片都加载完了, 那么进行一次回调就可以了.
if (++this.counter === this.imagesLength) {
this.$emit('imageLoad');
}
}
}
}
</script>
<style scoped>
#shop-item {
width: 100%;
display: flex;
font-size: 0;
padding: 5px;
border-bottom: 1px solid #ccc;
}
.item-selector {
width: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.item-title, .item-desc {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.item-img {
padding: 5px;
/*border: 1px solid #ccc;*/
}
.item-img img {
width: 80px;
height: 100px;
display: block;
border-radius: 5px;
}
.item-info {
font-size: 17px;
color: #333;
padding: 5px 10px;
position: relative;
overflow: hidden;
}
.item-info .item-desc {
font-size: 14px;
color: #666;
margin-top: 15px;
}
.info-bottom {
margin-top: 10px;
position: absolute;
bottom: 10px;
left: 10px;
right: 10px;
}
.info-bottom .item-price {
color: orangered;
}
</style>