1、父組件調用子組件的方法
思路:用戶點擊商品時商品詳情頁出現,因此須要 父組件中點擊商品時調用子組件一個方法來控制 商品詳情頁的 顯示 隱藏。
food組件
<template>
<div class="food" v-show="foodtoShow">
</div>
</template>
<script>
export default{
props: {
selcurfood: { // 用戶選擇的商品
type: Object
}
},
data(){
return {
foodtoShow :false // 定義商品詳情頁的初始狀態 開始 隱藏
}
},
methods:{
foodinforshow(){ // 待父組件 觸發了點擊事件,調用 foodinforshow 方法,顯示商品詳情頁
this.foodtoShow = true
}
}
}
</script>
父組件goods組件
此處省略 引入、註冊...
1.綁定事件
<li @click='selectFood(food,$event)' v-for="food in item.foods" class="food-item">...
2.應用組件
<food :selcurfood='selectedfoods' ref="food"></food>
selectedfoods 表明用戶當前點擊的商品
3.創建一個接受用戶選擇商品的對象
data (){
return {
goods: [],// goods json 數組
listHeight: [],// 存放 foods 內部的每一塊的高度
scrollY:0,
selectedfoods:{} // 接收用戶點擊的商品
}
},
4.點擊事件
selectFood(food,event){
if (!event._constructed) {// 阻止瀏覽器的原生 click 事件
return;
}
this.selectedfoods=food// 傳入用戶選擇的商品,存在selectedfoods 對象裏
this.$refs.food.foodinforshow() //利用 ref 屬性 來調用 子組件的方法
}
到這裏 就和商品詳情頁相關聯了!繼續...
2、實現詳情頁內容的滾動
1.實現滾動須要better-scroll,因此須要dom綁定,異步加載bscroll
<template>
<transition name="move">
<!-- v-show 控制顯示詳情頁 ref 實現dom 綁定-->
<div class="food" v-show="foodtoShow" ref='foodDetail'>
</div>
</transition>
</template>
import BScroll from 'better-scroll';
export default {
props: {
selcurfood: { // 用戶選擇的商品
type: Object
}
},
data(){
return {
foodtoShow :false, // 定義商品詳情頁的初始狀態 開始 隱藏
}
},
methods: {
show(){
this.showFlag = true;
this.$nextTick( () => { // 接口 保證dom 渲染完畢 異步添加滾動綁定
if(!this.scroll){
this.scroll=new BScroll(this.$refs.foodDetail,{
click:true
})
}else{
this.scroll.refresh()
}
})
},
foodtoHide(){ // 點擊返回 圖標 商品詳情消失
this.foodtoShow = false
},
}
注意:better-scroll 做用在 ref 下的 全部內容。因此要有foodcontent 來 包裹內容,不然better-scroll 不起做用,附上部分代碼。
<div class="food" v-show="foodtoShow" ref='foodDetail'>
<div class="foodcontent">
<div class="image-header">
<img :src="selcurfood.image" alt="" />
<div class="back" @click="foodtoHide">
<i class="icon-arrow_lift"></i>
</div>
</div>
<div class="content">
<h1 class="title">{{selcurfood.name}}</h1>
<div class="detail">
<span class="sell-count">月售{{selcurfood.sellCount}}</span>
<span class="rating">好評率{{selcurfood.rating}}</span>
</div>
<div class="price">
<span class="newPrice">¥{{selcurfood.price}}</span>
<span class="oldPrice"v-show='selcurfood.oldPrice'>¥{{selcurfood.oldPrice}}</span>
</div>
<!--引入cartcontrol組件,而且用一個div包裹他-->
<div class="cartcontrol-wrapper">
<cart-control :foodsele='selcurfood' @add="addFood"></cart-control>
</div>
<transition name='fade'>
<!--使用.stop.prevent阻止冒泡和默認事件,避免穿透-->
<div class="buy" v-show="!selcurfood.count || selcurfood.count===0" @click.stop.prevent="ballshow">
加入購物車
</div>
</transition>
</div>
<split-line v-show="selcurfood.info"></split-line>
<div class="info" v-show="selcurfood.info">
<h1 class="title">商品信息</h1>
<p class="text">{{selcurfood.info}}</p>
</div>
<split-line ></split-line>
<div class="rating">
<h1 class="title">商品評價</h1>
<ratingselect
:selectType="selectType"
:onlyContent="onlyContent" :desc="desc"
:ratings="selcurfood.ratings" >
</ratingselect>
</div>
</div>
</div>
3、購物車按鈕問題
問題:點擊加入購物車,出現小球,作拋物線動畫的位置從屏幕上方運動,而不是從點擊購物車處開始作運動
分析:當點擊加入購物車的按鈕時候(ballshow),food.count被添加了數據Vue.set(this.selcurfood, 'count', 1),因此加入購物車按鈕會被隱藏(v-show的display:none),但同時會執行this.$emit('add', event.target);,只不過這個是異步執行的,而且這個被異步執行的方法add是拋物線小球動畫計算初始目標高度的地方,因此當傳入的購物車按鈕被設置爲display:none的時候,動畫的目標初始高度沒法計算,就會去使用父層div的高度,從而影響了拋物線小球動畫效果.
解決辦法: 給點擊購物車這個按鈕消失時加一個動畫,從而vue有足夠的時間將數據傳遞到異步執行的方法,這樣就不會影響拋物線小球的動畫初始目標計算了。
<transition name='fade'>
<!--使用.stop.prevent阻止冒泡和默認事件,避免穿透-->
<div class="buy" v-show="!selcurfood.count || selcurfood.count===0" @click.stop.prevent="ballshow">
加入購物車
</div>
</transition>
點擊出現小球
<!--引入cartcontrol組件,而且用一個div包裹他-->
<div class="cartcontrol-wrapper">
<cart-control :foodsele='selcurfood' @add="addFood"></cart-control>
</div>
ballshow(event){ // 點擊加入購物車按鈕,傳入事件,小球出現
if (!event._constructed) {// pc 點擊 由於購物車按鈕在bscroll裏面,因此須要處理掉bscroll的事件類型
return;
}
// 拋物線小球動畫
this.$emit('add', event.target); //觸發當前實例food上的事件add(在goods組件上綁定在food組件的add方法)
Vue.set(this.selcurfood, 'count', 1);
},
addFood(target) { //跟add關聯的addFood方法
this.$emit('add', event.target); // 觸發當前實例food上的事件add(在goods組件上綁定在food組件的add方法)
}
引用解釋:兩次觸發當前實例的事件add是由於兩個操做都是同一個動做,這個動做是綁定在food組件上的add方法,而food組件會在goods組件中被導<food :selcurfood='selectedfoods' @add="addFood" ref="food"></food>,而在goods組件裏面,addFood方法就會指向當前goods組件的方法_drop,繼而使用shopcart的小球拋物線動畫this.$refs.shopcart.drop(target);,這樣就是實現了使用跨組件調用方法的效果.
4、商品評價
(1) 評價類型選擇
<ratingselect
:selectType="usrseleType"
:onlyContent="isonlyContent"
:curdesc="foodDesc"
:ratings="selcurfood.ratings"
@usrselect='usrseleRating'
@toggleSwitch='toggleContent' >
</ratingselect>
data(){
return {
foodtoShow :false, // 定義商品詳情頁的初始狀態 開始 隱藏
usrseleType: ALL, // 默認類型
isonlyContent: true, // 是否只看有內容的評價 默認 不看
foodDesc: { // 類型 對象
all: '所有',
positive: '推薦',
negative: '吐槽'
}
}
}
usrseleRating (type){ // 子組件 傳過來的事件
this.usrseleType = type
this.$nextTick(() => { // 每更改一次類型,dom 異步刷新一次
this.scroll.refresh();
});
},
toggleContent(){ // 切換顯示是否有內容的評價
this.isonlyContent=!this.isonlyContent
this.$nextTick(() => { //切換的時候須要從新刷新bscroll
this.scroll.refresh();
});
},
needShow(type,txt) {
if (this.isonlyContent && !txt) { // //只顯示有內容的 而且 沒有內容就返回false
return false;
}
if (this.usrseleType === ALL) { //顯示所有類型的評價
return true;
} else { // 顯示對應的類型的評價
return type === this.usrseleType;
}
}
usrseleRating和toggleContent使用異步$nextTick是由於vue是異步更新dom的,當改變了vue屬性時候,當前的dom不是當即更新的(會致使頁面的高度變化了,可是bscroll來不及更新,影響滾動體驗),而是會放進去異步更新隊列裏面等候更新,即便這個隊列的等待時間不長,可是也來不及立刻更新dom,因此使用$nextTick強制刷新這個隊列
在food.vue組件使用usrseleRating和toggleContent來更新food.vue組件的屬性,而不能在子組件ratingSelect裏面更新,由於vue限制了子組件不能更改父組件的屬性,因此經過使用相似this.$emit('select', type);來調用父組件的方法來更改
(2) 評價時間轉換
使用了vue 的過濾器
<div class="time">{{rating.time | formatDate}}</div>
filters: {
formatDate(time) {
let date = new Date(time);
//調用curTime模塊的formatDate函數來解析時間
return formatDate(date, 'yyyy-MM-dd hh:mm');
}
}
//在es6下,export 函數function的導入須要這樣寫
import { formatDate } from '../../common/js/date'; //導入自定義的date模塊
formatDate.js是一個自定義的js組件,不是vue組件,目錄位於:src/common/js,這種寫法是爲了練習js的模塊化編程
將單獨的一個函數寫成一個模塊
經過export導出函數
經過import導入函數
export function formatDate(date, fmt) { //在es6下導出一個函數
//對一個或多個y進行匹配,匹配到就進行年的替換(年有四位,因此須要特殊處理)
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
let o = {
'M+': date.getMonth() + 1, //js的月是從0開始算,因此要加1
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
};
//對月,日,時,分,秒進行匹配替換(這些都是兩位,能夠一塊兒處理)
for (let k in o) {
if (new RegExp(`(${k})`).test(fmt)) { //匹配到key例如MM
let str = o[k] + ''; //而後o['MM'] 就是date.getMonth() + 1
//若是匹配到的時間是1位數,例如是M,那麼就直接使用date.getMonth() + 1的值,
//若是是兩位數,那麼就在前面補0,使用padLeftZero函數
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
}
}
return fmt;
};
//先加兩個0,而後再根據長度截取(由於最長也就2個0的長度)
function padLeftZero(str) {
return ('00' + str).substr(str.length);
}
food組件到此差很少了。