zoukankan      html  css  js  c++  java
  • 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({
                "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组件化的购物车的实现,暂时不上动图了,有兴趣可以自己搭建环境粘贴赋值实现一下,有问题欢迎提出交流。

  • 相关阅读:
    20210907
    彻底解决Manjaro中的编程字体问题
    manjaro上安装腾讯会议
    应对github的新变换,更加方便应用github
    Failed to stop iptables.service: Unit iptables.service not loaded.
    centOS7给虚拟机设置固定ip地址
    centOS给虚拟机设置固定ip地址
    SpringBoot启动报错Failed to determine a suitable driver class
    Jasypt加解密
    java json字符串转JSONObject对象、转JAVA对象、转List<T>对象
  • 原文地址:https://www.cnblogs.com/ly-qingqiu/p/10476872.html
Copyright © 2011-2022 走看看