最近在学习vue,然后了解到这个框架的一个突出特点就是组件化,所以用这种形式实现了一个购物车,因为在实际项目中,数量加减可能不只在购物车里用到,所以把这个小的效果也提取出来了,在实现过程中形成了很多坑,这里记录一下,希望对大家能有所帮助。
tip1: 这里会用到使用的组件库是vux, 需要先安装(npm insatall vux --save npm install vux-loader --save-dev),然后具体怎么使用,如果不清楚请去看vux官网。
我把列表和底部的全选计数分别写成了组件。
tip2:这里涉及到了父子组件之间的传值,以及非父子组件之间的传值。其中父子组件之间的传值不在赘述,非父子组件之间传值,需要定义个公共的公共实例文件bus.js,作为中间仓库来传值,不然路由组件之间达不到传值的效果。
bus.js:
import Vue from "vue" export default new Vue()
cart父组件(这里是自己写死的数据,写在了父组件,这样就可以避免写在列表页要传给父组件,之后再由父组件传给footer组件的弊端):
html:
<template> <div id="cart"> <div class="contentWrapChild"> <cart-list :cartList="cartList" ></cart-list> <cart-footer :cartList="cartList" ></cart-footer> </div> </div> </template>
js:
import cartList from "./component/cartList.vue"; import cartFooter from "./component/cartFooter.vue" import Bus from "./../../assets/js/bus" export default{ data(){ return{ cartList:[ { id:1, img:"../../../assets/img/bindOwner.jpg", title:"中秋节茶月饼礼盒", spec:"规格:", priceNow:500, number:2, checked:false, stock:5, // 库存 index:0, }, { id:2, img:"../../../assets/img/shopIndex1.jpg", title:"fx参天眼药水", spec:"规格:", priceNow:45, number:2, checked:false, stock:5, index:1, }, { id:3, img:"../../../assets/img/shopIndex2.jpg", title:"牛奶沐浴乳", spec:"规格:", priceNow:20, number:2, checked:false, stock:5, index:2, } ], newCartData:[], } }, components:{ cartList, cartFooter } }
list组件(列表子组件):
<template> <div class="cartList"> <group ref="list"> <cell v-for="(item,index) in this.cartList" :key="index"> <div style="border-bottom:1px solid #e5e5e5"> <div class="child child-inp"> <input type="checkbox" class="choice" :checked="item.checked" @click="listSelect(item.id)"> </div> <div class="child child-img goodsImg"> <img :src="item.img"/> </div> <div class="child child-text"> <p class="title">{{item.title}}</p> <p class="weui-media-box__desc spec">{{item.spec}}</p> <p class="point"><span>{{item.priceNow}}</span>积分</p> </div> </div> <div style="text-align: right;margin-right:0.2rem;"> <num-choice :gsId="item.id" :count="item.number" :stock="item.stock" ></num-choice> <i class="icon iconfont icon-icon--" style="font-size:22px;vertical-align: text-bottom" @click="deleteGoods(item.id)"></i> </div> </cell> </group> </div> </template>
对应的样式:
.cartList >>> .weui-cells{ margin-top:0 } .cartList >>> .vux-cell-primary{ flex:none; } .cartList >>> .weui-cell__ft{ width:100%; height:100%; text-align: left; } .cartList >>> .weui-cell{ height:2.14rem; padding:0; } .cartList{ width:100%; .child{ display:inline-block; } .child-inp,.child-img{ height:100%; vertical-align: top; } .child-inp{ width:0.5rem; padding-left:0.3rem; input{ width: 0.38rem; height: 0.38rem; background: #fff; border: 1px solid #ddd; appearance: normal; -moz-appearance: button; -webkit-appearance: button; outline: none; border-radius: 2px; margin: 0.52rem 0 0 0; position: relative; vertical-align: middle; margin-right: 0.5rem; } } .goodsImg{ margin-right: .8em; width: 1.2rem; height: 1.2rem; line-height: 1.42rem; text-align: center; img{ width:100%; height:100%; } } .child-text{ margin: 0.22rem 0.2rem 0.1rem 0; .title{ font-size: 0.28rem; color: #999; line-height: 0.36rem; white-space: normal; } .spec{ font-size: 0.24rem; line-height:0.4rem; } .point{ font-size: 0.3rem; color: #ff0000; } } }
js: (注意自己项目的路径)
import NumChoice from "../../../components/numChoice.vue" import Bus from "./../../../assets/js/bus" // 和footer组件引入公共的bug,来做为中间传达的工具 export default{ data(){ return{ newGs:[], liCheckedStatus:false } }, components:{ NumChoice, Actionsheet, Group, XSwitch }, props:[ "cartList", ], methods:{ // 点击单个列表项:因为总结算数和总的积分数都要变化,所有要传值给CartFooter组件-----暂时用非父子组件之间的传值方法 // 1、 实现单个列表的反选 // 2 把选中的商品push到新的数组中去,方便传值给cartFooter组件 listSelect(gsId){ let gs = this.cartList; let newGs = this.newGs.splice(0,this.length); // 每次给newGs push的时候都先把newGs清空 for(let i in gs){ let item = gs[i]; if(item.id == gsId){ item.checked = !item.checked; } if(gs[i].checked){ newGs.push(item); } } // 把新得到的列表回传给footer组件 Bus.$emit("fromList",newGs) }, // 删除商品 deleteGoods(gsId){ let that = this; this.$vux.confirm.show({ title:"删除该商品", content:"是否确定删除该商品?", onConfirm () { let goodsArr = []; for (let i in that.cartList){ let item = that.cartList[i]; if(item.id == gsId){ that.cartList.splice(i,1); } } for(let j in that.cartList){ let item = that.cartList[j]; if(item.checked){ goodsArr.push(item); } } Bus.$emit("fromList",goodsArr) }, onCancel () { }, }) }, }, created(){ // 接收numberChoice组件回传回来的新的商品数量 Bus.$on("fromNumChoice",(data,id)=>{ let newGs = this.newGs; // 每次给newGs push的时候都先把newGs清空 for(let i in this.cartList){ if(id == this.cartList[i].id ){ // 判断点击的商品id和传过来的id一样,这件商品就改成选中状态,同时push到新的数组中,把新的数组传给footer组件 this.cartList[i].checked = true; this.cartList[i].number = data; } } let ckCertList = []; for(let i in this.cartList){ if(this.cartList[i].checked){ ckCertList.push(this.cartList[i]); } } Bus.$emit("fromList",ckCertList) // 回传新的列表 }) } }
footer组件(底部)
html:
<template> <div class="cartFooter"> <p class="prompt">积分余额:200</p> <div class="footer"> <label> <input type="radio" @click="allSelect" :checked="isAllChecked">全选 </label> <p class="pointAll">合计:{{totalPoint}}<span>积分</span></p> <a href="#" @click="settle" >去结算({{totalNumber}})</a> </div> </div> </template>
css:
.cartFooter{ width:100%; height:1.56rem; position:fixed; bottom:0; .prompt{ width: 100%; height: 0.58rem; font-size: 0.24rem; color: #ff0000; background: #999; line-height: 0.58rem; text-align: center; position: fixed; bottom: 0.98rem; /*z-index: 3;*/ } .footer{ width: 100%; height: 0.98rem; color: #3ccd58; background: #555; line-height: 0.98rem; display: flex; label{ font-size: 0.26rem; color: #fff; margin: 0 0.2rem; display: table; input{ width: 16px; height: 16px; background: #fff; border: 1px solid #ddd; /* margin-right: 5px; */ appearance: normal; -moz-appearance: button; -webkit-appearance: button; outline: none; border-radius: 2px; margin: 0 5px 3px 0; position: relative; vertical-align: middle; } } p{ font-size: 0.32rem; padding-right: 0.14rem; } a{ font-size: 0.3rem; color: #fff; background: #3ccd58; padding: 0; line-height: 0.98rem; flex: 1; } } }
js:
import Bus from "./../../../assets/js/bus" // 和cartList组件引入公共的bug,来做为中间传达的工具 export default{ data(){ return{ totalPoint:0, totalNumber:0, isAllChecked:false } }, props:{ cartList:Array, // 父组件传过来的数据 }, methods:{ // 点击全选按钮: // 1. 实现全选按钮的反选 // 2. 选中全选按钮时,所有列表项是选中状态,总结算数为列表的length; 合计积分数为每个列表项的数量*单个列表项的积分数的总和。否则就是全部未选中状态; allSelect(){ this.isAllChecked =!this.isAllChecked; // 取反 let list = this.cartList; this.totalPoint = 0; // 每次点击全选按钮的时候把总积分清零 for(let i in list){ let item = list[i]; item.checked = this.isAllChecked; if(list.length > 0 && item.checked){ this.totalNumber = list.length; this.totalPoint += item.number*item.priceNow; }else{ this.totalNumber = 0; this.totalPoint = 0; } } }, settle(){ if(this.totalNumber == 0){ this.$vux.toast.show({ "250px", type:'text', text:'您还没有选择需要结算的商品', position:"middle" }); } } }, created(){ // 接收列表页传过来的新的列表 Bus.$on("fromList",(data)=>{ let newGs = data; this.totalPoint = 0; // 每次计算之前先把总积分数清零 this.totalNumber = 0; for(let j in newGs){ if(newGs[j].checked){ this.totalPoint += newGs[j].number * newGs[j].priceNow; } } if(this.cartList.length != "" && this.cartList.length == newGs.length){ // 控制全选按钮的选中状态 用传过来的新列表的长度和原始列表的长度进行比较 this.isAllChecked = true; }else{ this.isAllChecked = false; } // 商品总件数为新列表的总长度 this.totalNumber = data.length; }) }, }
numChoice组件(商品加减组件):
html:
<template> <div class="numChoice"> <button class="sub" @click="sub">-</button> <input type="text" v-model='value'/> <button class="add" @click="add">+</button> </div> </template>
css:
.numChoice{ display:inline-block; vertical-align: super; } /*清除input默认样式*/ input{ outline:0; /*去掉谷歌自带的点就input框出现的边框情况*/ /*-webkit-appearance:button; 是元素标签看起来像个按钮,意思是定义了按钮样式*/ -webkit-appearance:none; /*去掉按钮样式*/ border-radius: 0; } .numChoice button,.numChoice input{ background-color: #fff; border: 1px solid #999; font-size: 17px; text-align: center; vertical-align: middle; -webkit-appearance : none ; /*解决iphone safari上的圆角问题*/ border-radius: 0; } .numChoice button{ width: 22px; height: 22px; line-height: 14px; color: #666; } .numChoice input{ width: 20px; height: 20px; line-height: 16px; color: #000; }
js:
import Bus from "./../assets/js/bus" // 和cartList组件引入公共的bug,来做为中间传达的工具 // 因为这个组件想写成公共组件 所以不应该做过多操作 只要加减数量变化,回传过去就好,其他根据数量变化发生的变化在列表页判断就行 export default{ data(){ return{ value:this.count, } }, props:["gsId","count","stock"], methods:{ add(){ this.value++; if(this.value >= this.stock){ this.value = this.stock; } Bus.$emit("fromNumChoice",this.value,this.gsId); // 数量改变后把发生改变的这一项和数量的变化告诉列表页 }, sub(){ this.value--; if(this.value <= 1){ this.value = 1; } Bus.$emit("fromNumChoice",this.value,this.gsId); } } }
以上就是使用vue组件化的购物车的实现,暂时不上动图了,有兴趣可以自己搭建环境粘贴赋值实现一下,有问题欢迎提出交流。