Vue(小案例_vue+axios仿手机app)_Vuex优化购物车功能

一、前言                                                                   

        1、用vuex实现加入购物车操作

        2、购物车详情页面

         3、点击删除按钮,删除购物详情页面里的对应商品

二、主要内容                                                            

 

1、用vuex加入购物车

  (1)在src目录下创建store.js,

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
       //vuex五大将
    state:{
        num:1//小球的数量默认为1
    },

    getters:{
        getShopNum(state){
            return state.num;
        }
    },

    mutations:{
        addShopNum(state,num){//增加小球数量
            state.num +=num;
        },

        changeShopNum(state,num){//改变小球数量
            state.num = num;
        }
    },

    actions:{
        addShopNumByAction({commit},num){
            commit('addShopNum',num);
        },

        changeShopNum({commit}, num){
            commit('changeShopNum',num)
        }
    }

})

  (2)在main.js入口文件中挂载,并且导入

import store from './store.js'

/* eslint-disableo-new */
new Vue({
  el: '#app',
  router,
  store,//一定要导入
  components: { App },
  template: '<App/>'
})

  (3)在app.vue(底部导航组件)中用computed监听这个pickNum

computed:{
    pickNum(){
      return this.$store.getters.getShopNum
    }
  }

  

  (4)在点击“加入购物车”那个组件, 

 afterEnter(){
                this.isExist=false; //显示出来之后执行这个,又将小球隐藏

               /* 不用这个$bus
               this.$bus.$emit('sendPickNum',this.pickNum);*/
                
                //用vuex, 触发action,
                this.$store.dispatch('addShopNumByAction',this.pickNum);
                //当触发了上面的事件之后,
                GoodsTool.add({
                    id:this.goodsInfo.id,
                    num:this.$store.getters.getShopNum
                })
            },

2、购物车详情页面(上面点击+++,下面也要变化)

  (1)在购物车详情页面,每次点加,减的时候让他去触发action

methods:{
            addNum(shop){//每次点击都接受到当前的对象
                shop.num++; //这里的值虽然加上了,但是,数据并没有响应上去,是因为created是一开始就加载的,后来点击修改了num的值,但是没有 响应视图
                this.$store.dispatch('addShopNumByAction',1)//触发action
                console.log(shop)
            },
            substract(shop){
                if(shop.num==1){
                    return;
                }

                shop.num--;
                this.$store.dispatch('addShopNumByAction',-1)//触发action

            }
}

  (2)要让底部导航栏里面的数量随着点击而发生变化

created(){
    //当你的组件一创建好了后就挂载这个bus公交车,$on这个事件监听
   /* this.$bus.$on(`sendPickNum`, (data)=>{
      this.pickNum=this.pickNum + data;
    }),

    this.pickNum=GoodsTool.getTotalCount();*/
    //触发action里面的changShop方法,并且将当前的总数量传给他
this.$store.dispatch('changeShopNum',GoodsTool.getTotalCount())

  }

3、点击删除按钮,删除购物详情页面里的对应商品

del(shop,index){//将当前的对象,和index传进来
                this.shopCart.splice(index,1)//数组中的当前对象
                delete GoodsTool[shop.id]
                GoodsTool.removeGoods(shop.id)

                let num = shop.um;
                this.$store.dispatch('addShopNumByAction',-num)

            }

4.通信的组件如下

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
    state:{
        num:1//小球的数量默认为1
    },

    getters:{
        getShopNum(state){
            return state.num;
        }
    },

    mutations:{
        addShopNum(state,num){
            state +=num;
        },

        changeShopNum(state,num){
            state.num = num;
        }
    },

    actions:{
        addShopNumByAction({commit},num){
            commit('addShopNum',num);
        },

        changeShopNum({commit}, num){
            commit('changeShopNum',num)
        }
    }

})
store.js
let obj={};

//这里需要存储数据
//{商品的id, 商品的数量}

//保存商品
obj.saveGoods = function(goodsList){
    window.localStorage.setItem('goodsList',JSON.stringify(goodsList))
}


//获取商品的值,没有值传一个空对象

obj.getGoodsList = function(){
    return JSON.parse(window.localStorage.getItem('goodsList'|| '{}'))
}

//增加商品
obj.add = function(goods){
    let goodsList = this.getGoodsList()//获取到storage里面的对象
    if(goodsList[goods.id]){

        //goods.id是商品的数量,对应有值的话就追加
        goodsList[goods.id] = goodsList[goods.id] + goods.num;
    }else{
        goodsList[goods.id]=goods.num;
    }

    //传进来之后还需要保存
    this.saveGoods(goodsList);
} 

//获取购物车的总数量
obj.getTotalCount = function(){
    let goodsList = this.getGoodsList();
    let values = Object.values(goodsList);//Object.values返回的是一个数组,里面对应着每一个key的value
    let sum = 0;
    values.forEach(val => sum = sum +val);
    return sum;

}

//删除
obj.removeGoods=function(id){
    let goodsList = this.getGoodsList();
    delete goodsList[id];
    return this.saveGoods(goodsList)
}
export default obj;
GoodsTool.js
<template>
    <div>
        <go-back-header title="商品详情"></go-back-header>
        <div class="outer-swiper">
            <div class="swiper">
                我真的是轮播图
            </div>
        </div>
        <div class="product-desc">
            <ul>
                <li><span class="product-desc-span">
                    商品标题
                </span></li>
                <li class="price-li">市场价:
                    <s>¥{{goodsInfo.market_price}}</s> 销售价:<span>¥{{goodsInfo.sell_price}}</span></li>
                <li class="number-li">购买数量:<span @click='substract'>-</span><span>{{pickNum}}</span><span @click='add'>+</span></li>
                <li>
                    <mt-button type="primary">立即购买</mt-button>
                    <mt-button type="danger" size='small' @click='ballHandler'>加入购物车</mt-button>
                </li>
            </ul>
        </div>
            <transition name='ball' @after-enter='afterEnter'>
                <div class="ball" v-if="isExist"></div>
            </transition>
            <!--<div class="ball" v-if='isExist'></div>-->
        <div class="product-props">
            <ul>
                <li>商品参数</li>
                <li>商品货号:{{goodsInfo.goods_no}}</li>
                <li>库存情况:{{goodsInfo.stock_quantity}}件</li>
                <li>上架时间:{{goodsInfo.add_time}}</li>
            </ul>
        </div>
        <div class="product-info">
            <ul>
                <li>
                    <mt-button type="primary" size="large" plain @click.native='showShopInfo()'>图文介绍</mt-button>
                </li>
                <li>
                    <mt-button type="danger" size="large" plain @click.native=''>商品评论</mt-button>
                </li>
            </ul>
        </div>
    </div>
</template>
<script>
    import GoodsTool from './GoodsTool.js'
    export default{
        name:'GoodsDetail',
        data(){
            return{
                url:`getthumImages/${this.$route.params.id}`,
                goodsInfo:{},//当前购物车的信息,里面有id
                pickNum:1 ,
                isExist:false //让小球默认是隐藏的状态, 
            }
        },

        methods:{

            afterEnter(){
                this.isExist=false; //显示出来之后执行这个,又将小球隐藏

               /* 不用这个$bus
               this.$bus.$emit('sendPickNum',this.pickNum);*/
                
                //用vuex, 触发action,
                this.$store.dispatch('addShopNumByAction',this.pickNum);
                //当触发了上面的事件之后,
                GoodsTool.add({
                    id:this.goodsInfo.id,
                    num:this.$store.getters.getShopNum
                })
            },


            //点击加入购物车执行这个方法,然后让小球显示出来
            ballHandler(){

                this.isExist=true;
              //  this.$bus.$emit('sendPickNum',this.pickNum); //将当前的pickNum传过去,但是这个不能加在这里,否者一点击“加入购物车就传
            },
            
            add(){
                //如果当前的数小于库存数,就让他做加
                if(this.pickNum < this.goodsInfo.stock_quantity){
                    this.pickNum++;
                }

                
            },

            substract(){
                if(this.pickNum ===1){ //减法的时候最少为1,当此时值为1的时候不做操作
                    return;
                }

                this.pickNum--;
            },



            showShopInfo(){

                //通过动态路由进行路由跳转
                this.$router.push({
                    name:"photo.info",
                    query:{
                        id:this.$route.params.id
                    }
                })
            },

            

            shopComment(){
                this.$router.push({
                    name:'good.comment',
                    query:{
                        id:this.$route.params.id
                    }
                })
            }

        },

        created(){
           //每个商品都只有一个对应的id,
            this.$axios.get(`goods/getinfo${this.$route.params.id}`)
            .then(res=>{
                this.goodsInfo = res.data.message[0]
            })
            .catch(err=>{
                console.log('商品详情异常',err)
            });

            this.pickNum = GoodsTool.getTotalCount();//获取购物的所有总数

        }
    }
</script>
<style scoped>
.ball-enter-active {
    /*给1s的时间让小球进入动画效果*/
    animation: bounce-in 1s;
}

.bass-leave{
    /*元素进入以后,透明度0,整个动画都是0*/
    /*元素离开默认是1,所以会闪一下,设置为0*/
    opacity: 0;

}

@keyframes bounce-in {
    0% {
        transform: translate3d(0, 0, 0);
    }
    50% {
        transform: translate3d(140px, -50px, 0);
    }
    75% {
        transform: translate3d(160px, 0px, 0);
    }
    100% {
        transform: translate3d(140px, 300px, 0);
    }
}

.swiper {
    border: 1px solid rgba(0, 0, 0, 0.2);
    margin: 8px;
     95%;
    border-radius: 15px;
    overflow: hidden;
}

.outer-swiper,
.product-desc,
.product-props,
.product-info {
    border-radius: 5px;
    border: 1px solid rgba(0, 0, 0, 0.2);
    margin: 3px;
}


/*请ulpadding*/

.outer-swiper ul,
.product-desc ul,
.product-props ul,
.product-info ul {
    padding: 0;
}

.product-desc ul li,
.product-props ul li,
.product-info ul li {
    list-style: none;
    font-size: 15px;
    color: rgba(0, 0, 0, 0.5);
    margin-top: 8px;
}

.product-desc ul >:nth-child(1) span {
    color: blue;
    font-size: 22px;
    font-weight: bold;
}

.product-desc ul >:nth-child(1) {
    border-bottom: 1px solid rgba(0, 0, 0, 0.5);
}

.product-desc ul,
.product-info ul,
.product-props ul {
    padding-left: 10px;
}

.price-li span {
    color: red;
    font-size: 25px;
}

.price-li s {
    margin-right: 16px;
}


/*加减*/

.number-li span {
    display: inline-block;
    border: 2px solid rgba(0, 0, 0, 0.1);
     25px;
}


/*商品参数*/

.product-props ul >:nth-child(1) {
    text-align: left;
}

.product-props ul:not(:nth-child(1)) {
    margin-left: 40px;
}

.product-info ul {
    margin-bottom: 70px;
    padding: 0 5;
}

.number-li span {
    text-align: center;
}

.number-li >:nth-child(2) {
     40px;
}

.ball {
    border-radius: 50%;
    background-color: red;
     24px;
    height: 24px;
    position: absolute;
    top: 440px;
    left: 120px;
    display: inline-block;
    z-index: 9999;
}
</style>
GoodsDetail.vue
<template>
  <div id='app'>
    <!--顶部-->
     <mt-header title="信息管理系统" fixed>
     <router-link to="/" slot="left">
       <mt-button icon="back">返回</mt-button>
       </router-link>
       <mt-button icon="more" slot="right"></mt-button>
     </mt-header>


   



    <!--底部-->
    <div class="tabBar">
      <ul>
        <li v-for="(tab, index) in tabs">
          <router-link :to="tab.routerName">
            <img :src="tab.imgSrc">
            <!--小球-->
            <mt-badge size='small' color="#FC0107" v-if='index===2'>{{pickNum}}</mt-badge>
            <p>{{tab.title}}</p>
          </router-link>
        </li>
      </ul>
      
    </div>
   <router-view></router-view>

  </div>
    
</template>

<script>
  import index from './assets/index.png'
  import vip from './assets/vip.png'
  import shopcart from './assets/shopcart.png'
  import search from './assets/search.png'
  
  let tabs = [
    {id:1, title:"首页", imgSrc:index, routerName:{name:'home'}},
    {id:2, title:"会员", imgSrc:vip, routerName:{name:'vip'}},
    {id:3, title:"购物车", imgSrc:shopcart, routerName:{name:'cart'}},
    {id:4, title:"查找", imgSrc:search, routerName:{name:'search'}}


  ]

  import GoodsTool from './GoodsTool.js'
export default {

  name: 'App',
  data(){
    return {
     
      tabs:tabs,
      //pickNum:0,//底部栏小球

    }
  },

 watch:{
  selected:function(newV,oldV){
    
    console.log(newV);
    console.log(oldV);
    console.log(this.selected);//id绑定的id
    this.$router.push({name:this.selected});

  },

  computed:{
    pickNum(){
      return this.$store.getters.getShopNum
    }
  }

  created(){
    //当你的组件一创建好了后就挂载这个bus公交车,$on这个事件监听
   /* this.$bus.$on(`sendPickNum`, (data)=>{
      this.pickNum=this.pickNum + data;
    }),

    this.pickNum=GoodsTool.getTotalCount();*/
    this.$store.dispatch('changeShopNum',GoodsTool.getTotalCount())

  }
 }
}
</script>

<style scoped>
.tabBar{
   100%;
  height: 55px;
  background-color: #ccc;
  position: absolute;
  bottom: 0;
  left: 0;
  background-image: linear-gradient(180deg, #d9d9d9, #d9d9d9 50%, transparent 50%);
   background-size: 100% 1px;
  background-repeat: no-repeat;/*做一像素渐变线*/
  background-position: top left;
  background-color: #fafafa;
}

.tabBar ul{
   100%;
  overflow: hidden;
}

.tabBar ul li{
  float: left;
   25%;
  height: 55px;
  text-align: center;
}

.tabBar ul li a{
  display: inline-block;
   100%;
  height: 100%;
 padding-top: 10px;

}
.tabBar ul li a.link-active{
  background-color: pink;
  position: relative;
}
.tabBar ul li a img{
   25px;
  height: 25px;
}
.tabBar ul li a p{
  font-size: 12px;
}

/*重写一下小球的颜色*/
.mint-bage.is-size-small{
  position: absolute;
  top: 0;
  right: 10px;
}
</style>
App.vue
<template>
    <div>
        <div class="pay-detail">
            <ul>
                <li class="p-list" v-for="(shop, index) in shopCart">
                    <mt-switch></mt-switch>
                    <img src="">
                    <div class="pay-calc">
                        <p>{{shop.title}}</p>
                        <div class="calc">
                            <span>¥777</span>
                            <span @click="substract(shop)">-</span>
                            <span>{{shop.num}}</span>
                            <span @click="addNum(shop)">+</span>
                            <a href="javascript:;">删除</a>
                        </div>
                    </div>
                </li>
            </ul>
        </div>
        <div class="show-price">
            <div class="show-1">
                <p>总计(不含运费):</p>
                <span>已经选择商品1件,总价¥888元</span>
            </div>
            <div class="show-2">
                <mt-button type="danger" size="large">去结算</mt-button>
            </div>
        </div>
    </div>
</template>
<script>
    import GoodTool from '@/GoodsTool.js'
    export default {
        name:'Cart',
        data(){
            return{
                shopCart:[]

            }
        },
        methods:{
            addNum(shop){//每次点击都接受到当前的对象
                shop.num++; //这里的值虽然加上了,但是,数据并没有响应上去,是因为created是一开始就加载的,后来点击修改了num的值,但是没有 响应视图
                this.$store.dispatch('addShopNumByAction',1)
                console.log(shop)
            },
            substract(shop){
                if(shop.num==1){
                    return;
                }

                shop.num--;
                this.$store.dispatch('addShopNumByAction',-1)

            },

            del(shop,index){//将当前的对象,和index传进来
                this.shopCart.splice(index,1)//数组中的当前对象
                delete GoodsTool[shop.id]
                GoodsTool.removeGoods(shop.id)

                let num = shop.um;
                this.$store.dispatch('addShopNumByAction',-num)

            }

        },
        computed:{
            payment(){
                let total = 0;//定义总金额
                let count =0;//定义总数量

                this.shopCart.forEach((shop)=>{
                    if(shop.isSelected){
                        count = count+shop.num;
                        total = total + shop.num * shop.sell_price;
                    }
                })

                return {
                    total, count
                }
            }
        },
        
    

        created(){
            let goodsList = GoodsTool.getGoodsList();//{"88":5,"99":4} 第一个数商品id 第二个是商品数量
            let ids = Object.key(goodsList).join(',');//Object.key()获取key值 [88,99]
            if(ids){
                this.$axios.get(`goods/getshopcarlist${ids}`)
                .then(res=>{

                    this.shopCart=res.data.message;//返回的是一个数组
                  //给数组元素添加属性
                  for(var i=0; i<this.shopCart.length;i++){
                      let shop=this.shopCart[i];//获取到当前的对象
                      let num = goodsList[shop.id];//根据当前对象的id查找到对应的购物车数量

                      if(num){
                          shop.num = num;

                          this.$set(shop, 'num', num);//自己给这个数据进行双向数据绑定
                          this.$set(shop,'isSelected',true);

                      }

                  }
                })
            }
        }
    }
</script>
<style scoped>
.pay-detail ul li {
    list-style: none;
    border-bottom: 1px solid rgba(0, 0, 0, 0.2);
    margin-bottom: 3px;
}

.pay-detail ul {
    padding-left: 5px;
    margin-top: 5px;
}

.pay-detail ul li img {
     80px;
    height: 80px;
    display: inline-block;
    vertical-align: top;
    margin-top: 10px;
}

.pay-detail ul li >:nth-child(1),
.pay-detail ul li >:nth-child(3) {
    display: inline-block;
}

.pay-calc p {
    display: inline-block;
     250px;
    overflow: hidden;
    color: blue;
    font-size: 15px;
    margin-bottom: 10px;
}

.pay-detail ul li >:nth-child(1) {
    line-height: 80px;
}

.calc:nth-child(1) {
    color: red;
    font-size: 20px;
}

.calc span:not(:nth-child(1)) {
    border: 1px solid rgba(0, 0, 0, 0.3);
    display: inline-block;
     20px;
    text-align: center;
}

.calc a {
    margin-left: 20px;
}

.show-1,
.show-2 {
    display: inline-block;
}

.show-1,
.show-2 {
    margin-left: 30px;
}

.show-price {
    background-color: rgba(0, 0, 0, 0.2);
}
</style>
Cart.vue

三、总结                                                                   

虽然现在走得很慢,但不会一直这么慢
原文地址:https://www.cnblogs.com/xxm980617/p/10725439.html