Vue ( 四 ) shopCart 组件开发

简介: 其中 deliveryPrice 和 minPrice 的数据都是从 data.json数据 中 seller 对象下 获得。所以在goods 组件中还要 获取到 seller对象 的数据,否则会报错:

一、shopCart组件

(1) goods 父组件和 子组件 shopCart 传参


deliveryPrice:{ // 单价 从json seller 对象数据中获取
	type:Number,
	default:0
},
minPrice:{  // 最低起送价 从json seller 对象数据中获取
	type:Number,
	default:20
}

其中 deliveryPrice 和 minPrice 的数据都是从 data.json数据 中 seller 对象下 获得。所以在goods 组件中还要 获取到 seller对象 的数据,否则会报错:

[Vue warn]: Error in render: "TypeError: Cannot read property 'deliveryPrice' of undefined"

解决方法:根组件 App.vue 中 router-view 组件获取seller 数据,传到 goods 组件中
1-1.app.vue (根组件 也是 goods 的父组件)
<keep-alive>
  <router-view :sell="sellerObj"></router-view>
</keep-alive>
注意:sellerObj 是data 定义 的 对象里用来接收 data.json 数据,相当于 实参
1-2.goods.vue (相对于跟组件的子组件 且 shopCart 的父组件)
通过props 属性 进行组件之间的通信

props: {
    sell: Object  // 相当于 形参
  },

1-3.shopCart.vue ( goods 的子组件)

 <shopCart :delivery-price="sell.deliveryPrice" :min-price="sell.minPrice"></shopCart>

(2) 选中商品 的 计算功能

1-1. 传入用户选中商品的集合
说明:从父组件会 传入一个用户选中商品的 数组,数组里会存放着 n 个对象,每个对象里存放着该 商品的 价格 和 数量。

props:{             // 通过父组件传过来的  ( 相当于形参 )
 selefoodsArr:{     // 用户选中的商品存放在一个数组里   接收的是 data.json数据的 goods(数组)
	type:Array, // 当父组件传过来的 类型是对象或者 是数组时, default 就是一个函数
	default (){
		return []    // 返回数组 存放着选中 商品 对应的 goods下的 foods 数组(由 父组件 的 实参 决定的返回值)
	}
}

1-2. 利用计算属性 选中商品数量的变化,商品总价,动态改变描述等功能
computed:{
	totalPrice (){          //计算总价,超过起送额度后提示可付款
		let total=0      // 定义一个返回值
		this.selefoodsArr.forEach((rfoods) =>{ //  遍历 这个 goods 数组  取到 价格 和 数量 (当然在这里数据库没有count 这个属性,稍后 我们会利用 vue.set() 新建一个count 属性)
			total += rfoods.price * rfoods.count // 形参 rfoods  实参 是 foods
		});
		return total;
	},
	totalCount (){      //  //计算选中的food数量,在购物车图标处显示,采用绝对定位,top:0;right:0;显示在购物车图标右上角   
		let count=0
		this.selefoodsArr.forEach((rfoods) =>{ //  形参 rfoods  实参 是 foods
			count += rfoods.count
		});
		return count;
	},
	payDesc (){        //控制底部右边内容随food的变化而变化,payDesc()控制显示内容,enough 添加类调整显示样式
		let diff = this.minPrice - this.totalPrice
	      if (!this.totalPrice) {
	        return `¥${this.minPrice}起送`
	      } else if (diff > 0) {
	        return `还差¥${diff}元`
	      } else {
	        return '去结算'
	      }
	}			
}

这样就渲染到 template 里了


<div class="shopCart">
 <div class="content">
   <div class="content-left">
	<div class="logo-wrapper">		
	 <!--徽章 展示选中商品的个数-->
	 <div class="badge" v-show="totalCount">
		{{totalCount}}
	 </div>
	 <!--购物车 图标  选择商品和未选择商品 时 动态改变 样式  条件:只要选择了商品即总价不为0 ,样式变-->  
	  <div class="logo" :class="{'active':totalCount}">
	    <i class="icon-shopping_cart"></i>
	   </div>
	</div>
	<!--同理: 总价 不为0 字体高亮-->
	<div class="price" :class="{'active':totalPrice}">
	   ¥{{totalPrice}}
	</div>
	<!--配送费 data.json 提供-->
	<div class="desc">
	   另需要配送费¥{{deliveryPrice}}元
	</div>
   </div>
   <!--根据条件   动态 改变样式-->
   <div class="content-right" :class="{'enough':totalPrice>=minPrice}">				
	{{payDesc}}			
  </div>
 </div>
</div>

相关样式


&.active
    color white
    
&.enough
    background #00b43c
    color white

总结:通过以上学习我们能发现,selectFoods()的变化起着关键作用,它的变化会引起DOM的变化,并最终体现到界面上,而我们不用关注DOM内部的具体实现,这就是vue的一大好处。如果采用jQuery完成这些功能会略显繁杂。

二、cartControl 组件

说明:这个组件是控制购物车小球的。其中涉及到小球的动画

(1) 新增属性 count

说明:
  • 在goods 下的 foods 添加一个属性 count,用来存储用户选中的商品个数,计算商品总价 以及 关联徽章(显示用户选择商品的个数)的变化

  • 方法:通过import Vue from 'vue';使用set接口,通过vue.set()添加属性,当它变化时就能被检测到,从而父组件能获取到count值(遍历选中的商品时使用)


methods:{
  addCart(event){ //  点击count 加,
    //console.log(event.target);
	if (!event._constructed) { // 去掉自带click事件的点击
        return;
      }
	if(!this.foodsele.count){
		Vue.set(this.foodsele, 'count', 1)
	}else{
		this.foodsele.count++
	}				
  },
  decreaseCart (event){ //  点击减少
	if (!event._constructed) { // 去掉自带click事件的点击
        return;
        }
	if(this.foodsele.count){
		this.foodsele.count --
	  }		
    }
}

(2)添加按钮 实现transtion 过渡

我们要实现的效果是:当点击添加按钮时,减少按钮出现 并伴随着 旋转、平移以及透明度变化的 一些 动画效果

<transition name='move'> <!--平移动画--> 		
	<div class="cart-decrease" v-show="foodsele.count" @click='decreaseCart($event)'>
	  <span class="icon-remove_circle_outline inner"></span><!--旋转、透明度动画--> 
   </div>
</transition>

 .cart-decrease
    display inline-block
    padding 6px
    transition: all .4s linear   /*过渡效果的 CSS 属性的名称、过渡效果需要多少时间、速度效果的速度曲线*/   
    .inner
      line-height 24px
      font-size 24px
      color rgb(0,160,220)
      transition all 0.4s linear
    &.move-enter-active, &.move-leave-active
      transform translate3d(0,0,0)  /* 这样可以开启硬件加速,动画更流畅,3D旋转,X轴位移24px */
      .inner     
        display inline-block    /* 设置成inline-block才有高度,才能有动画 */
        transform rotate(0)
    &.move-enter, &.move-leave-active
      opacity: 0
      transform translate3d(24px,0,0)
      .inner
        transform rotate(180deg)

三、抛物线小球动画

通过两个层来控制小球,外层控制一个方向的变化,内层控制另外一个方向的变化(写两层才会有抛物线的效果),采用fixed布局(是相对于视口的动画)

事件发射和接收

组件之间传值-1

组件之间传值-2

扩展

Vue1.0组件间传递

  • 使用$on()监听事件;
  • 使用$emit()在它上面触发事件;
  • 使用$dispatch()派发事件,事件沿着父链冒泡;
  • 使用$broadcast()广播事件,事件向下传导给所有的后代

(1) Vue2.0 组件之间传递数据

1-1. 当点击 添加数量时 在 cartControl 组件里的 addCount 方法里 通过 $emit 属性 派发一个事件 , 传入点击的对象

addCart(event){ //  点击count 加,
//				console.log(event.target);
	if (!event._constructed) { // 去掉自带click事件的点击
        return;
      }
	if(!this.foodsele.count){
		Vue.set(this.foodsele, 'count', 1)
	}else{
		this.foodsele.count++
	}
// 当点击 添加数量时  通过 $emit 属性 提交一个名为 add 给父组件
// 子组件通过 $emit触发 add事件 ,将参数传递给父组件
	this.$emit('add', event.target);
}

1-2. 操作 goods 组件
购物车组件如果提交了addCart事件就调用add函数

<cart-control :foodsele='food' @add="addFood"></cart-control>

父组件使用 @add="addFood"监听由子组件vm.$emit触发的事件,通过addFood()接受从子组件传递过来的数据,通知父组件数据改变了。

addFood(target) {
    this._drop(target);
}

1-3. 父组件访问子组件 vue 提供了接口 ref
<shopCart ref="shopCart" :delivery-price="sell.deliveryPrice" :min-price="sell.minPrice" :selefoods-arr='selectfoods'  ></shopCart>

_drop(target) {
    // 体验优化,异步执行下落动画
    this.$nextTick(() => {
      this.$refs.shopCart.balldrop(target);// 将target传入shopCart子组件中的balldrop方法,所以drop方法能获得用户点击按钮的元素,即能获取点击按钮的位置
    });
}      
区别 访问DOM 变量
1-3. 操作 shopCart 组件

data (){ //  定义一个数组 来 控制小球的状态   定义多个对象,表示页面中做多同时运动的小球
  return{  //  定义 5 个 小球   
	balls:[{show:false},{show:false},{show:false},{show:false},{show:false}],
	dropBalls:[] //  接收下落小球
   }
}
methods:{
 balldrop(ele) {
// console.log(el)  取到点击 对象
      for(var i=0;i<this.balls.length;i++){
      	 let ball=this.balls[i]
      	 if(!ball.show){
      	 	ball.show=true
      	 	ball.ele=ele
      	 	this.dropBalls.push(ball)
      	 	return;
      	 }
      }                
  }
}

动画过程开始,利用vue 提供的钩子函数

beforeEnter (el){  //找到所以设为true的小球
	let count=this.balls.length
	while(count--){
		let ball = this.balls[count];
		if(ball.show){
			let pos=ball.el.getBoundingClientRect() //返回元素相对于视口偏移的位置
			let x=pos.left-32   // 点击的按钮与小球(fixed)之间x方向的差值
			let y=-(window.innerHeight-pos.top-22)
			 el.style.display = '';    //设置初始位置前,手动置空,覆盖之前的display:none,使其显示
             el.style.webkitTransform = `translate3d(0,${y}px,0)`;  //外层元素做纵向的动画,y是变量
             el.style.transform = `translate3d(0,${y}px,0)`;
             let inner = el.getElementsByClassName('inner_hook')[0];//内层元素做横向动画,inner-hook(用于js选择的样式名加上-hook,表明只是用                                                                      //于js选择的,没有真实的样式含义)
             inner.style.webkitTransform = `translate3d(${x}px,0,0)`;
             inner.style.transform = `translate3d(${x}px,0,0)`;
		}
	}
 },
     enter(el) {  
     /* eslint-disable no-unused-vars */
      let rf = el.offsetHeight;
      this.$nextTick(() => {//异步执行
      el.style.webkitTransform = 'translate3d(0,0,0)';    //重置回来
      el.style.transform = 'translate3d(0,0,0)';
      let inner = el.getElementsByClassName('inner_hook')[0];
      inner.style.webkitTransform = 'translate3d(0,0,0)';
      inner.style.transform = 'translate3d(0,0,0)';
    });
  },
  afterEnter(el) {
    let ball = this.dropBalls.shift();  //取到做完动画的球,再置为false,即重置,它还可以接着被利用
    if (ball) {
      ball.show = false;
      el.style.display = 'none';
    }
  }
<div class="ball-container">
    <div v-for="ball in balls">
      <transition name="drop" @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
        <div class="ball" v-show="ball.show">
          <div class="inner inner_hook"></div>
        </div>
      </transition>
    </div>
</div>
 &.drop-enter,&.drop-enter-active
        transition all 0.4s cubic-bezier(0.49,-0.29,0.75,0.41)
        .inner
          width 16px
          height 16px
          border-radius 50%
          background rgb(0,160,220)
          transition all 0.4s linear

原文发布时间为:2018年01月25日
原文作者:前端喵

本文来源:开源中国 如需转载请联系原作者


目录
相关文章
|
10天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
3天前
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发
|
13天前
|
JavaScript
Vue 指令速查表
【10月更文挑战第12天】Vue 指令速查表
|
3天前
|
存储 JavaScript
Vue 状态管理工具vuex
Vue 状态管理工具vuex
|
10天前
|
缓存 JavaScript 搜索推荐
Vue SSR(服务端渲染)预渲染的工作原理
【10月更文挑战第23天】Vue SSR 预渲染通过一系列复杂的步骤和机制,实现了在服务器端生成静态 HTML 页面的目标。它为提升 Vue 应用的性能、SEO 效果以及用户体验提供了有力的支持。随着技术的不断发展,Vue SSR 预渲染技术也将不断完善和创新,以适应不断变化的互联网环境和用户需求。
29 9
|
9天前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
8天前
|
JavaScript
如何在 Vue 中使用具名插槽
【10月更文挑战第25天】通过使用具名插槽,你可以更好地组织和定制组件的模板结构,使组件更具灵活性和可复用性。同时,具名插槽也有助于提高代码的可读性和可维护性。
13 2
|
8天前
|
JavaScript
Vue 中的插槽
【10月更文挑战第25天】插槽的使用可以大大提高组件的复用性和灵活性,使你能够根据具体需求在组件中插入不同的内容,同时保持组件的结构和样式的一致性。
12 2
|
8天前
|
前端开发 JavaScript 容器
在 vite+vue 中使用@originjs/vite-plugin-federation 模块联邦
【10月更文挑战第25天】模块联邦是一种强大的技术,它允许将不同的微前端模块组合在一起,形成一个统一的应用。在 vite+vue 项目中,使用@originjs/vite-plugin-federation 模块联邦可以实现高效的模块共享和组合。通过本文的介绍,相信你已经了解了如何在 vite+vue 项目中使用@originjs/vite-plugin-federation 模块联邦,包括安装、配置和使用等方面。在实际开发中,你可以根据自己的需求和项目的特点,灵活地使用模块联邦,提高项目的可维护性和扩展性。
|
9天前
|
JavaScript 前端开发 UED
vue 提高 tree shaking 的效果
【10月更文挑战第23天】提高 Vue 中 Tree shaking 的效果需要综合考虑多个因素,包括模块的导出和引用方式、打包工具配置、代码结构等。通过不断地优化和调整,可以最大限度地发挥 Tree shaking 的优势,为 Vue 项目带来更好的性能和用户体验。