一、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日
原文作者:
前端喵本文來源:
開源中國如需轉載請聯系原作者