<!--规格产品的弹出层 s--> <section> <!--点击选规格后弹出的遮罩层--> <transition name="fade"> <div class="specs_cover" @click="showChooseList" v-if="showSpecs"></div> </transition> <!--规格产品的弹出层 e--> <!--展开层 s--> <transition name="fadeBounce"> <div class="specs_list" v-if="showSpecs"> <!--header s--> <header class="specs_list_header"> <!--标题(当前已选择规格商品的名字)--> </header> <!--绘制关闭规格弹出层的叉号 --> <svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" version="1.1" class="specs_cancel" @click="showChooseList"> <line x1="0" y1="0" x2="16" y2="16" stroke="#666" stroke-width="1.2"/> <line x1="0" y1="16" x2="16" y2="0" stroke="#666" stroke-width="1.2"/> </svg> <!--header e--> <!--规格详情 s--> <section class="specs_details"> <!--显示的文字为"规格"--> <h5 class="specs_details_title"> {{choosedFoods.specifications[0].name}} </h5> <!--规格list s--> <ul> <!--specsIndex当前选中规格的索引值--> <li :class="{specs_activity: itemIndex == specsIndex}" v-for="(item,itemIndex) in choosedFoods.specifications[0].values" @click="chooseSpecs(itemIndex)"> {{item}} </li> </ul> <!--规格list e--> </section> <!--footer显示价格和加入购物车--> <footer> <div class="specs_price"> <span>¥</span> <!--specsIndex:当前所选规格的下标对应的index--> <span>{{choosedFoods.specfoods[specsIndex].price}}</span> </div> <!--加入购物车,点击的时候,执行addSpecs,传入参数: category_id-食品分类id item_id-食品id food_id-食品规格id name-食品名字 price-食品价格 specs-食品规格 --> <div class="specs_addto_cart" @click="addSpecs( choosedFoods.category_id, choosedFoods.item_id, choosedFoods.specfoods[specsIndex].food_id, choosedFoods.specfoods[specsIndex].name, choosedFoods.specfoods[specsIndex].price, choosedFoods.specifications[0].values[specsIndex], choosedFoods.specfoods[specsIndex].packing_fee, choosedFoods.specfoods[specsIndex].sku_id, choosedFoods.specfoods[specsIndex].stock)"> 加入购物车 </div> </footer> <!--规格详情 e--> </div> </transition> <!--展开层 e--> </section>
说明:
showSpecs:控制显示上商品规格的弹出层,初始弹出层不显示,showSpecs值为false
showChooseList:点击时经过改变showSpecs的值,来改变规格弹出层的显示和隐藏
choosedFoods和foods的值相同,其数据结构以下:
规格list(循环展现):
chooseSpecs:该方法用于记录当前所选规格的索引值vue
不可删除,点击减号会出现提示语,提示信息以下:git
<transition name="fade"> <p class="show_delete_tip" v-if="showDeleteTip"> 多规格商品只能去购物车删除哦 </p> </transition>
说明:
showDeleteTip:多规格商品点击减按钮。弹出提示框。showDeleteTip的初始值为falsegithub
<!--showMoveDot的初始值为[],点击+号以后,值为[true,true]--> <transition appear @after-appear="afterEnter" @before-appear="beforeEnter" v-for="(item,index) in showMoveDot" :key="index"> <!--值为true以后会显示小球--> <span class="move_dot" v-if="item"> <!--绘制小球--> <svg class="move_liner"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#cart-add"></use> </svg> </span> </transition>
##### 说明
(1)加减按钮:
listenInCart:监听圆点是否进入购物车
showChooseList:是否显示规格列表
showReduceTip:是否显示"没法减去规格"商品的提示语
showMoveDotFun:显示下落小球
(2)totalNum:购物车中总共的商品数量
shopDetailData:商铺详情数据
经过web
this.shopDetailData = await shopDetails( this.shopId, this.latitude, this.longitude );
方法拿到,其数据结构以下:
(3)foods:
来源于menuList其中的某一项,其数据结构以下:
(4)clearCart:触发,点击购物车商品列表的"清空"
(5)toggleCartList:触发,点击购物车图标,展开已加入购物车中的商品列表
(6)menuList食品列表:
经过
this.menuList = await foodMenu(this.shopId);
方法得到,其数据结构以下:
(7)Object.keys,返回器枚举自身属性的对象,
返回值一个表示给定对象的全部可枚举属性的字符串数组
处理对象:
处理字符串:
处理数组:
(8)foodItem的结构:
来源于initCategoryNum方法。该方法的做用在于在初始化、点击+/-商品时。获取加入购物车中的商品数量。
(细节部分不是很明白[○・`Д´・ ○]理解仍是不是很深刻)数组
export default{ data(){ //geohash位置信息 geohash: "", //购物车商品列表 cartFoodList:[], //商铺详情数据 shopDetailData:null, //总价格 totalPrice:0, //商店id值 shopId: null, //当前选中的食品数据 choosedFoods: null, //控制显示食品规格 showSpecs:false, //当前选中的规格索引值 specsIndex: 0, //多规格商品点击减按钮。弹出提示框,初始值为false,也就是默认不弹出 showDeleteTip:false, //显示购物车商品列表(初始值为false,不显示,有加入购物车的商品时,点击购物车图标,来控制其显示仍是隐藏) showCartList:false, //已加入购物车的商品列表 cartFoodList: [], //控制下落的小圆点的显示隐藏 showMoveDot: [], //购物车下落圆球是否抵达指定位置 receiveInCart:false, //商品右上角已加入购物车的数量 categoryNum:[], //食品列表,初始值为[] menuList:[] }, computed:{ ...mapState(["latitude", "longitude", "cartList"]), //购物车中商品总数量 totalNum:function(){ //初始状态下cartFoodList的值为[],totalNum的值为0 let num=0; this.cartFoodList.forEach(item=>{ num+=item.num; }); return num; }, //minimumOrderAmount还差多少元起送 minimumOrderAmount:function(){ //拿到了shopDetailData以后执行 if(this.shopDetailData){ //还差多少元起送=最低起送价-总价 return this.shopDetailData.float_minimum_order_amount - this.totalPrice; } else{ return null; } }, //当前商铺购物信息 shopCart:function(){ return {...this.cartList[this.shopId]} } }, created(){ //坐标 this.geohash=this.$route.query.geohash; //商铺id this.shopId=this.$route.query.id; this.INIT_BUYCART(); }, methods:{ ...mapMutations([ //加入购物车的方法 'ADD_CART', //移出购物车的方法 'REDUCE_CART', //初始化购物车 'INIT_BUYCART', //清空购物车 'CLEAR_CART', ]), ... //点击"选规格"的时候执行-显示规格列表 showChooseList(foods){ if(foods){ //choosedFoods已选中的食品数据就等于当前点击项,所在item的list this.choosedFoods=foods; }, //规格弹出层的"显示"/"隐藏"切换 this.showSpecs=!this.showSpecs; //当前选中的规格索引值 //重置当前所选中规格的索引值 this.specsIndex=0; }, //控制显示没法减去规格商品的提示语 showReduceTip(){ this.showDeleteTip=true; clearTimeout(this.timer); this.timer=setTimeout(()=>{ //3秒以后清空定制器 clearTimeout(this.timer); //将提示语进行隐藏 this.showDeleteTip=false; },3000); }, //记录当前所选规格的索引值,(目的)在于给当前所选项添加active状态的class chooseSpecs(index){ this.specsIndex=index; }, /*加入购物车:传入参数 category_id-食品分类id item_id-食品id food_id-食品规格id name-食品名字 price-食品价格 specs-食品规格 */ addToCart(category_id, item_id, food_id, name, price, specs){ //this.ADD_CART方法在mutations.js中定义 this.ADD_CART({ //店铺id shopid:this.shopId, //食品分类id category_id, //食品id item_id, //食品规格id food_id, //食品名字 name, //食品价格 price, //食品规格 specs }); }, //移出购物车: removeOutCart(category_id, item_id, food_id, name, price, specs){ this.REDUCE_CART({ //店铺id shopid:this.shopId, //食品分类id category_id, //食品id item_id, //食品规格id food_id, //食品名字 name, //食品价格 price, //食品规格 specs }); }, //多规格商品加入购物车 /*传入参数 category_id-食品分类id item_id-食品id food_id-食品规格id name-食品名字 price-食品价格 specs-食品规格 packing_fee:打包费 sku_id:库存id stock:库存 */ addSpecs(category_id, item_id, food_id, name, price, specs, packing_fee, sku_id, stock){ this.ADD_CART({shopid:this.shopId, category_id, item_id, food_id, name, price, specs, packing_fee, sku_id, stock }); //规格商品加入购物车以后,隐藏规格的弹出层 this.showChooseList(); }, //清空购物车 clearCart(){ //点击了 this.CLEAR_CART(this.shopId); //隐藏已展现的购物车列表 this.toggleCartList(); }, //控制已加入购物车的商品列表的显示和隐藏 toggleCartList(){ //cartFoodList已加入购物车的商品列表,初始值为[] //showCartList的值控制是否显示列表 this.cartFoodList.length ? this.showCartList=!this.showCartList:true }, //显示下落小球 showMoveDotFun(showMoveDot, elLeft, elBottom){ //点击加号以后,this.showMoveDot的值从[],变成true,下落的小圆球显示 this.showMoveDot=[...showMoveDot,...showMoveDot]; //应该是初始状态小球的位置吧 this.elLeft=elLeft; this.elBottom=elBottom; }, //动画初次渲染前 beforeEnter(el){ //初次渲染的时候,肯定小球的位置 // 设置transform值 el.style.transform = `translate3d(0,${37 + this.elBottom - this.windowHeight}px,0)`; el.children[0].style.transform = `translate3d(${this.elLeft - 30}px,0,0)`; //设置不透明度 el.children[0].style.opacity = 0; }, //动画渲染后(让小球从当前位置移动到底部) afterEnter(el){ el.style.transform=`translate3d(0,0,0)`; el.children[0].style.transform=`translate3d(0,0,0)`; el.style.transition = 'transform .55s cubic-bezier(0.3, -0.25, 0.7, -0.15)'; el.children[0].style.transition = 'transform .55s linear'; //到达底部以后把下落的小球隐藏起来 this.showMoveDot = this.showMoveDot.map(item => false); //设置不透明度 el.children[0].style.opacity = 1; //判断是否已经到达底部 el.children[0].addEventListener('transitionend',()=>{ this.listenInCart(); }); el.children[0].addEventListener('webkitAnimationEnd',()=>{ this.listenInCart(); }); }, //监听小圆球是否进入购物车 listenInCart(){ //receiveInCart的初始值为false,用来判断下落的小圆球是否抵达指定位置 if(!receiveInCar){ this.receiveInCart=true; this.$refs.cartContainer.addEventListener('animationend',()=>{ this.receiveInCart=false; }); this.$refs.cartContainer.addEventListener('webkitAnimationEnd',()=>{ this.receiveInCart = false; }); } }, /*初始化和shopCart变化时,从新湖区购物车改变过的数据, 赋值 categoryNum,totalPrice,cartFoodList,整个数据流是自上而下的形式, 商品右上角已加入购物车的数量categoryNum[] totalPrice:总价格,初始值是0 cartFoodList:已加入购物车的商品列表初始值是[] */ initCategoryNum(){ //一个空数组 let newArr=[]; //初始状态下,已加入购物车的商品数量 let cartFoodNum=0; //已加入购物车的商品总价,初始值为0 this.totalPrice=0; //已加入购物车的商品列表 this.cartFoodList=[]; this.menuList.forEach((item,index)=>{ if(this.shopCart&&this.shopCart[item.foods[0].category_id]){ let num=0; Object.keys(this.shopCart[item.foods[0].category_id]).forEach(itemid=>{ Object.keys(this.shopCart[item.foods[0].category_id][itemid]).forEach(foodid=>{ let foodItem = this.shopCart[item.foods[0].category_id][itemid][foodid]; num+=foodItem.num; //item.type==1能够说明什么吗? if(item.type==1){ this.totalPrice+=foodItem.num*foodItem.price; if(foodItem.num>0){ this.cartFoodList[cartFoodNum] = {}; this.cartFoodList[cartFoodNum].category_id = item.foods[0].category_id; this.cartFoodList[cartFoodNum].item_id = itemid; this.cartFoodList[cartFoodNum].food_id = foodid; this.cartFoodList[cartFoodNum].num = foodItem.num; this.cartFoodList[cartFoodNum].price = foodItem.price; this.cartFoodList[cartFoodNum].name = foodItem.name; this.cartFoodList[cartFoodNum].specs = foodItem.specs; cartFoodNum ++; } } }); }) //已加入购物车的商品数量 newArr[index] = num; } else{ newArr[index] = 0; } this.totalPrice = this.totalPrice.toFixed(2); this.categoryNum = [...newArr]; }) }, ... }, watch:{ //showLoading变化时说明组件已经获取初始化数据,在下一帧nextTick进行后续操做 showLoading:function(value){ if(!value){ this.$nextTick(()=>{ //获取食品列表的高度 this.getFoodListHeight(); //初始化和shopCart变化时,从新获取购物车改变过的数据 this.initCategoryNum(); }); } }, //当前商店购物信息 shopCart:function(value){ this.initCategoryNum(); }, //购物车列表发生变化,没有商铺时隐藏 //cartFoodList:购物车商品列表 cartFoodList:function(value){ if(!value.length){ this.showCartList = false; } } } }
mutations.js中定义的方法:数据结构
//引入常量 import { SAVE_GEOHASH, RECORD_ADDRESS, ADD_CART, REDUCE_CART, CLEAR_CART, BUY_CART, SAVE_SHOPID, INIT_BUYCART, RECORD_SHOPDETAIL } from './mutation-types.js' import { setStore, getStore } from '../config/mUtils'; export default { //保存geohash [SAVE_GEOHASH](state, geohash) { state.geohash = geohash; }, //记录当前具体的位置信息 [RECORD_ADDRESS](state, { latitude, longitude }) { state.latitude = latitude; state.longitude = longitude; }, //加入购物车 [ADD_CART](state, { shopid, category_id, item_id, food_id, name, price, specs, packing_fee, sku_id, stock }) { let cart = state.cartList; let shop = cart[shopid] = (cart[shopid] || {}); let category = shop[category_id] = (shop[category_id] || {}); let item = category[item_id] = (category[item_id] || {}); if (item[food_id]) { item[food_id]['num']++; } else { item[food_id] = { "num" : 1, "id" : food_id, "name" : name, "price" : price, "specs" : specs, "packing_fee" : packing_fee, "sku_id" : sku_id, "stock" : stock }; } state.cartList = {...cart}; //存入localStorage setStore('buyCart', state.cartList); }, //移出购物车 [REDUCE_CART](state, { shopid, category_id, item_id, food_id, name, price, specs, }) { let cart = state.cartList; let shop = (cart[shopid] || {}); let category = (shop[category_id] || {}); let item = (category[item_id] || {}); if (item && item[food_id]) { if (item[food_id]['num'] > 0) { item[food_id]['num']--; state.cartList = {...cart}; //存入localStorage setStore('buyCart', state.cartList); } else { //商品数量为0,则清空当前商品的信息 item[food_id] = null; } } }, //清空当前商品的购物车信息 [CLEAR_CART](state,shopid){ state.cartList[shopid] = null; state.cartList = {...state.cartList}; setStore('buyCart', state.cartList); }, //保存商铺id [SAVE_SHOPID](state, shopid) { state.shopid = shopid; }, //初始化购物车 [INIT_BUYCART](state){ let initCart=getStore('buyCart'); if(initCart){ state.cartList=JSON.parse(initCart); } }, //RECORD_SHOPDETAIL [RECORD_SHOPDETAIL](state,detail){ state.shopDetail=detail; } }
参照项目地址:地址app