vue框架下的组件化的购物车实现

Posted ly-qingqiu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue框架下的组件化的购物车实现相关的知识,希望对你有一定的参考价值。

最近在学习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({
            width:"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组件化的购物车的实现,暂时不上动图了,有兴趣可以自己搭建环境粘贴赋值实现一下,有问题欢迎提出交流。

以上是关于vue框架下的组件化的购物车实现的主要内容,如果未能解决你的问题,请参考以下文章

Vue实现购物小球抛物线的方法实例

vuex 实现vue中多个组件之间数据同步以及数据共享。

vue.js组件之j间的通讯一 子组件接受父祖件数据

懂编译真的可以为所欲为|不同前端框架下的代码转换

VUE实现购物车界面

vue学习vue-cli3开发单文件组件