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

原文地址:https://www.cnblogs.com/ly-qingqiu/p/10476872.html

时间: 2024-10-13 03:09:27

vue框架下的组件化的购物车实现的相关文章

Vue框架下的node.js安装教程

Vue框架下的node.js安装教程 python服务器.php  ->aphche.java ->tomcat.   iis -->它是一个可以运行JAVASCRIPTR 的运行环境 -->它可以作为后端语言(websocket \ ) --强大的包管理工具npm,可以使用它安装插件 -->VUE框架是基于node.js平台运行的 --->它是基于chrome浏览器的V8引擎,运行速度快,性能高效 安装淘宝镜像:npm install cnpm -g --regist

vue框架下实现字数过多隐藏,鼠标移入显示功能

vue框架下实现字数过多隐藏,鼠标移入显示功能 html: <em class="c-300" @mouseenter="dItem && dItem.length>0 ? mouseen($event):''">{{dItem.substring(3)}}</em> <em class="c-310 hidden" v-if="dItem && dItem.lengt

JQUERY下的组件化编程之基本框架

var PROWEBAPPS = (function(){ var module = { ViewManager:(function(){ var submodule = { activate:function(){ console.log("main..."); } }; return submodule; })(), }; return module; })(); $(document).ready(function(){       PROWEBAPPS.ViewManager.

Vue全家桶之组件化开发

作者 | Jeskson 掘金 | https://juejin.im/user/5a16e1f3f265da43128096cb 学习组件化开发,首先掌握组件化的开发思想,组件的注册方式,组件间的数据交互方式,组件插槽的用法,vue调式工具的用法,组件的方式来实现业务逻辑功能. 组件化开发思想,组件注册,组件调式,组件间的数据交互,组件插槽 组件化规范,就是多的可能性重用 全局组件注册语法 Vue.component(组件名称, { data: 组件数据, template: 组件模板内容 }

vue框架下,echarts图表外部容器宽度设置为百分比时内容超出容器以及echarts图表自适应的实现方案

在vue.js框架下,创建echarts图表,当图表的外部容器的宽度设置为百分比时,出现图表内容宽度超出其外部容器的情况,几番调试,得解决方案如下: (备注说明:问题产生的具体原因尚不清晰,后面若知晓了再补充,但是初期解决问题阶段一定不要钻牛角,不是一开始就能或者可以知道真理的) 在创建了echart对象之后,就调用一次echart的resize方法,同时,获取图表数据的方法应该是异步的,如果是初期调试模拟也要用setTimeout延迟一下,0秒即OK了. methods: { ...mapAc

vue render函数 函数组件化

之前创建的锚点标题组件是比较简单,没有管理或者监听任何传递给他的状态,也没有生命周期方法,它只是一个接受参数的函数 在这个例子中,我们标记组件为functional,这意味它是无状态(没有data),无实例(没有this上下文) 一个函数化组件就像这样: Vue.component('my-component', { functional: true, // 为了弥补缺少的实例 // 提供第二个参数作为上下文 render: function (createElement, context) {

vue(7)—— 组件化开发 — webpack(1)

引子 在研究完前面的vue开发后,其实已经可以自己开发点东西了,靠前面的指令集,组件,还有vue-router,还有异步请求这些知识点,是完全可以开发出来的,完全可以达到时下前后端分离的效果. 但是,你在开发的过程当中,可能会遇到很多问题,而你只能用很低端的手段来解决问题,也可能在开发中,会遇到大量重复代码,而且还没有个很好的解决办法,并且在以后实际开发中,并不会像前面那样,一个html页面就搞定所有了,绝对不会的,肯定会有很多个文件,各种库,各种包之类的,这里倒过来,那里倒过去,哪个放前面,哪

vue框架之自定义组件中使用v-model

通常 vue在html常见表单空间支持v-model双向绑定例如 1 <input v-model="message" placeholder="edit me"> 2 <p>Message is: {{ message }}</p> 1,当我们自定义组件时如何使用v-model? 答:代码实例如下 2 我们在自定义组件中使用v-model的目的是什么? 答:自定义模板上定义v-mode目的是向外传递值,也就是向他的父组件传递值,

Vue入门五、组件化开发

创建组件的两种方式: 1.局部组件声明.注册和使用: 方法一(推荐). <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script type="text/javascript" src=&quo