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) } } })
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;
<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; width: 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); width: 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) { width: 40px; } .ball { border-radius: 50%; background-color: red; width: 24px; height: 24px; position: absolute; top: 440px; left: 120px; display: inline-block; z-index: 9999; } </style>
<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{ width: 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{ width: 100%; overflow: hidden; } .tabBar ul li{ float: left; width: 25%; height: 55px; text-align: center; } .tabBar ul li a{ display: inline-block; width: 100%; height: 100%; padding-top: 10px; } .tabBar ul li a.link-active{ background-color: pink; position: relative; } .tabBar ul li a img{ width: 25px; height: 25px; } .tabBar ul li a p{ font-size: 12px; } /*重写一下小球的颜色*/ .mint-bage.is-size-small{ position: absolute; top: 0; right: 10px; } </style>
<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 { width: 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; width: 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; width: 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>
三、总结
虽然现在走得很慢,但不会一直这么慢