項目總結:vue.js2.5餓了麼APP(6)主要組件實現 - 評價頁+商家頁部分

說明:本總結來源於慕課網 @ustbhuangyi老師的課程《Vue.js2.5+cube-ui重構餓了麼App》課程,本博客做了項目總結梳理便於回顧,需要學習的夥伴可以移步學習。與君共勉!

之前章節傳送: 

項目總結:vue.js2.5餓了麼APP(1)概述+項目準備 

項目總結:vue.js2.5餓了麼APP(2)主要組件實現 - 頭部相關組件

項目總結:vue.js2.5餓了麼APP(3)主要組件實現 - 購物車相關組件(上)

項目總結:vue.js2.5餓了麼APP(4)主要組件實現 - 購物車相關組件(下)

項目總結:vue.js2.5餓了麼APP(5)主要組件實現 - 商品詳情頁部分


速看

1. 商家評價頁面 商家評價頁面主要是顯示商家評分以及評論,複用了rating-select組件。其中需要從seller接口獲取評價數據。

2. 商家詳情頁面 商家詳情頁主要包括:商傢俱體信息(評分 銷量)+ 公告活動 + 商家實景 + 商家額外信息 + 頁面收藏。其中收藏頁面本地緩存的設計思想:收藏的效果就是,點擊按鈕切換圖標的顯示狀態,但是問題是,當再次刷新頁面,狀態會被重新初始化。由於項目只提供了一個商家的信息,並且不包含id。因此,希望通過url解析出包含id值的一個對象,在每次請求時加上id進行模擬收藏效果。爲了實現本地存儲,使用了封裝了sessionstorage的storage庫,提供了set get接口,和直接調用原生api 需要對對象和字符串進行轉換相比,使用起來更簡單,可以直接對對現象操作。導出兩個接口saveLocal和loadLocal,來實現本地緩存。saveload中構造一個複雜對象,第一層是id,它還是一個對象,包含收藏和其他需要存儲的項,然後返回該對象。最後的效果就是:點擊收藏之後會在localstorage中添加一個__seller__,會顯示id,以及favorite的true false。


目錄

一、商家評價頁面(ratings)

1. 概述

2. 佈局

3. 實現

(1)數據獲取

(2)滑動的實現

(3)評價部分

(4)使用rating-select組件

(5)優化

二、商家詳情頁面(seller)

1.概述

2.佈局

3. 實現

(1)滾動的實現

(2)收藏頁面設計思路

(3)區分id

(4)添加收藏方法

(5)本地緩存實現思路

(6)導出存儲通用接口

(7)存儲方法使用


一、商家評價頁面(ratings)

1. 概述

商家評價頁面主要是顯示商家評分以及評論,複用了rating-select組件。其中需要從seller接口獲取評價數據。

2. 佈局

評價頁主要分兩個部分:整體介紹(評分)+ 評價部分(rating-select組件 + ratings列表)

 <cube-scroll class="ratings" :data="computedRatings" :options="scrollOptions">
        <div class="ratings-content">
            <div class="overview">
                <div class="overview-left">
                    <h1 class="score">{{seller.score}}</h1>
                    <div class="title">綜合評分</div>
                    <div class="rank">高於周邊商家{{seller.rankRate}}%</div>
                </div>
                <div class="overview-right">
                    <div class="score-wrapper">
                        <span class="title">服務態度</span>
                        <star :size="36" :score="seller.serviceScore"></star>
                        <span class="score">{{seller.serviceScore}}</span>
                    </div>
                    <div class="score-wrapper">
                        <span class="title">商品評分</span>
                        <star :size="36" :score="seller.foodScore"></star>
                        <span class="score">{{seller.foodScore}}</span>
                    </div>
                    <div class="delivery-wrapper">
                        <span class="title">送達時間</span>
                        <span class="delivery">{{seller.deliveryTime}}</span>
                    </div>
                </div>
            </div>
            <split></split>
            <rating-select
                :ratings="ratings"
                :onlyContent="onlyContent"
                :selectType="selectType"
                @select="onSelect"
                @toggle="onToggle"
                v-if="ratings.length">
            </rating-select>
            <div class="rating-wrapper">
                <ul>
                    <li
                        v-for="(rating, index) in computedRatings"
                        :key="index"
                        class="rating-item border-bottom-1px">
                        <div class="avatar">
                            <img width="28" height="28" :src="rating.avatar">
                        </div>
                        <div class="content">
                            <h1 class="name">{{rating.username}}</h1>
                            <div class="star-wrapper">
                                <star :size="24" :score="rating.score"></star>
                                <span class="delivery" v-show="rating.deliveryTime">{{rating.deliveryTime}}</span>
                            </div>
                            <p class="text">{{rating.text}}</p>
                            <div class="recommend" v-show="rating.recommend && rating.recommend.length">
                                <span class="icon-thumb_up"></span>
                                <span
                                    class="item"
                                    v-for="(item, index) in rating.recommend"
                                    :key="index">{{item}}
                                </span>
                            </div>
                            <div class="time">{{format(rating.rateTime)}}</div>
                        </div>
                    </li>
                </ul>
            </div>
        </div>
    </cube-scroll>

3. 實現

(1)數據獲取

對於整體介紹部分overview部分的數據都是通過seller取的。

爲了拿到評價的數據,注意需要導出接口,而ratings有單獨接口,在index.js中寫入,並在ratings組件中導入import { getRatings } from 'api'

import { get } from './helpers'

const getSeller = get('/api/seller')
const getGoods = get('/api/goods')
const getRatings = get('/api/ratings')

export {
  getSeller,
  getGoods,
  getRatings
}

getRatings何時使用:會在每次tab切換的時候執行fetch鉤子方法(獲取數據)

因此在ratings中添加方法fetch,避免多次獲取數據(只獲取一次就可以了)

fetch() {
            if (!this.fetched) {
                this.fetched = true
                getRatings().then((ratings) => {
                    this.ratings = ratings
                })
            }
        },

(2)滑動的實現

爲了實現滑動,使用了cube-scroll組件,並作默認設置(防止橫向縱向滾動有衝突)

props: {
        data: {
            type: Object
        }
    },
data() {
        return {
            ratings: [],
            scrollOptions: {
                click: false,
                directionLockThreshold: 0
            }
        }
    },

(3)評價部分

商家評論列表部分和商品評價部分設計思路相同,但是沒有做成大的評論組件,在於DOM結構還是有區別的。

(4)使用rating-select組件

新建ratings的mixin抽離出評價的公共部分,組件的使用如佈局所示

const ALL = 2

export default {
    data() {
        return {
            onlyContent: true,
            selectType: ALL
        }
    },
    computed: {
        computedRatings() {
            const ret = []
            this.ratings.forEach((rating) => {
                if (this.onlyContent && !rating.text) {
                    return
                }
                if (this.selectType === ALL || this.selectType === rating.rateType) {
                    ret.push(rating)
                }
            })
            return ret
        }
    },
    methods: {
        onSelect(type) {
            this.selectType = type
        },
        onToggle() {
            this.onlyContent = !this.onlyContent
        }
    }
}

(5)優化

當點擊評價時先展開rating-select然後渲染出評價部分,但是這樣時不友好的,當沒有ratings時是沒有rating-select的,因此在組件上添加v-if="ratings.length",這時,只有評論加載完成之後纔會渲染rating-select組件。

二、商家詳情頁面(seller)

1.概述

商家詳情頁主要包括:商傢俱體信息(評分 銷量)+ 公告活動 + 商家實景 + 商家額外信息 + 頁面收藏。

收藏頁面本地緩存的設計思想:收藏的效果就是,點擊按鈕切換圖標的顯示狀態,但是問題是,當再次刷新頁面,狀態會被重新初始化。由於項目只提供了一個商家的信息,並且不包含id。因此,希望通過url解析出包含id值的一個對象,在每次請求時加上id進行模擬收藏效果。爲了實現本地存儲,使用了封裝了sessionstorage的storage庫,提供了set get接口,和直接調用原生api 需要對對象和字符串進行轉換相比,使用起來更簡單,可以直接對對現象操作。導出兩個接口saveLocal和loadLocal,來實現本地緩存。saveload中構造一個複雜對象,第一層是id,它還是一個對象,包含收藏和其他需要存儲的項,然後返回該對象。最後的效果就是:點擊收藏之後會在localstorage中添加一個__seller__,會顯示id,以及favorite的true false。

2.佈局

商傢俱體信息(評分 銷量)+ 公告活動 + 商家實景 + 商家額外信息

  

其中使用了star組件 split組件等,主要是靜態頁面,沒有着重要說的部分

<cube-scroll class="seller" :options="sellerScrollOptions">
        <div class="seller-content">
            <div class="overview">
                <h1 class="title">{{seller.name}}</h1>
                <div class="desc border-bottom-1px">
                    <star :size="36" :score="seller.score"></star>
                    <span class="text">({{seller.ratingCount}})</span>
                    <span class="text">月售{{seller.sellCount}}單</span>
                </div>
                <ul class="remark">
                    <li class="block">
                        <h2>起送價</h2>
                        <div class="content">
                            <span class="stress">{{seller.minPrice}}</span>元
                        </div>
                    </li>
                    <li class="block">
                        <h2>商家配送</h2>
                        <div class="content">
                            <span class="stress">{{seller.deliveryPrice}}</span>元
                        </div>
                    </li>
                    <li class="block">
                        <h2>平均配送時間</h2>
                        <div class="content">
                            <span class="stress">{{seller.deliveryPrice}}</span>元
                        </div>
                    </li>
                </ul>
                <div class="favorite">
                    <span class="icon-favorite" :class="{'active':favorite}"></span>
                    <span class="text">{{favoriteText}}</span>
                </div>
            </div>
            <split></split>
            <div class="bulletin">
                <h1 class="title">公告與活動</h1>
                <div class="content-wrapper border-bottom-1px">
                    <p class="content">{{seller.bulletin}}</p>
                </div>
                <ul v-if="seller.supports" class="supports">
                    <li
                        class="support-item border-bottom-1px"
                        v-for="(item, index) in seller.supports"
                        :key="index">
                        <support-ico :size=4 :type="seller.supports[index].type"></support-ico>
                        <span class="text">{{seller.supports[index].description}}</span>
                    </li>
                </ul>
            </div>
            <split></split>
            <div class="pics">
                <h1 class="title">商家實景</h1>
                <cube-scroll class="pic-wrapper" :options="picScrollOptions">
                    <ul class="pic-list">
                        <li class="pic-item"
                            v-for="(pic, index) in seller.pics"
                            :key="index">
                        <img :src="pic" width="120" height="90">
                        </li>
                    </ul>
                </cube-scroll>
            </div>
            <split></split>
            <div class="info">
                <h1 class="title border-bottom-1px">商家信息</h1>
                <ul>
                    <li
                        class="info-item border-bottom-1px"
                        v-for="(info, index) in seller.infos"
                        :key="index">
                    {{info}}
                    </li>
                </ul>
            </div>
        </div>
    </cube-scroll>

3. 實現

(1)滾動的實現

使用cube-scroll組件實現滾動,其中商家實景部分也需要使用cube-scroll,使用縱向滾動,其中橫向滾動時每一個li的佈局是inline-block,並且配置scrollX: true, 並且阻止滾動的冒泡。橫向滾動和縱向滾動不影響。其中默認配置:

data() {
        return {
            favorite: false,
            sellerScrollOptions: {
                directionLockThreshold: 0,
                click: false
            },
            picScrollOptions: {
                scrollX: true,
                stopPropagation: true,
                directionLockThreshold: 0
            }
        }
    },

(2)收藏頁面設計思路

思路:在收藏商家的時候,不同的商家回有不同的標識如id,在url中有一個字段來區分(如?id=1或?id=2)對於請求不同的數據,商家信息也是不同的,因此需要解析出url中的id參數作爲異步接口的參數告訴服務端服務端通過接口拿到id再根據不同的id取不同數據返回給前端。(但是由於本項目中模擬數據是相同的,因此返回的數據也是一樣的)

(3)區分id

1)可以手寫解析或者使用query-string庫,npm安裝之後在app.vue寫入import qs from 'query-string'

data() {
    return {
      seller: {
        id: qs.parse(location.search).id
      }
    }
  },

2)這時就可以拿到id,爲了將id作爲接口的請求參數傳遞給服務端,因此修改方法 ,此時可以看到id作爲參數已經傳入

methods: {
    _getSeller() {
      getSeller({
        id: this.seller.id
      }).then((seller) => {
        this.seller = seller
      })
    }
  },

3)同理,修改其他組件goods組件 ratings組件。之後可以看到效果seller和goods都可以傳入參數,但是ratings沒有,

fetch() {
            if (!this.fetched) {
                this.fetched = true
                getGoods({
                    id: this.seller.id
                }).then((goods) => {
                this.goods = goods
            })
            }
        },
fetch() {
            if (!this.fetched) {
                this.fetched = true
                getRatings({
                    id: this.seller.id
                }).then((ratings) => {
                    this.ratings = ratings
                })
            }
        },

4)修改vue.config.js文件中的devServer部分代碼,從而實現了前後端聯動

// 修改前
app.get('/api/seller', function (req, res) {
        res.json({
          errno: 0,
          data: seller
        })
      })
// 修改後
app.get('/api/seller', function (req, res) {
        const id = req.query.id
        res.json({
          errno: 0,
          data: Object.assign({}, seller, { id })
        })
      })

(4)添加收藏方法

文案“收藏”“已收藏”以及效果切換,但是效果是刷新頁面就會丟失

toggleFavorate() {
            this.favorite = !this.favorite
        }

(5)本地緩存實現思路

在common中添加js文件,並創建storage.js

對於項目擴展可以使用本地緩存去存儲其他東西,都可以複用通用存儲的功能。

使用一個good-storage庫,提供了set get接口,並且對sessionstorage做了封裝,好處在於相對於直接使用原生API,存在兩個問題:(1)首先需要測試storage可不可以用,在隱身模式操作loaclstorage和sessionstorageAPI會報錯。(2)當存儲對象的時候,需要先把對象轉化爲字符串存儲,並且獲取數據時需要利用json.pase轉換成對象。

(6)導出存儲通用接口

在商家頁面定義一個SELLER_KEY

導出兩個接口:saveToLocal(id, key, value)。邏輯:構造一個複雜對象,首先第一層是id,用於區分不同的商家,他是一個對象(因爲對於一個商家可以存儲不同的key,比如收藏favorite、評價等),因此先取到它,然後對其修改,然後把對象作爲整體存儲。

loadFromLocal(id, key, def)。邏輯:讀取時如果讀不到,直接取空對象。如果沒有定義id就返回默認值,如果定義了id,如果沒有key就取默認值,有key就返回key

import storage from 'good-storage'

const SELLER_KEY = '__seller__'

export function saveToLocal(id, key, val) {
    const seller = storage.get(SELLER_KEY, {})
    if (!seller[id]) {
        seller[id] = {}
    }
    seller[id][key] = val
    storage.set(SELLER_KEY, seller)
}

export function loadFromLocal(id, key, def) {
    const seller = storage.get(SELLER_KEY, {})
    if (!seller[id]) {
        return def
    }
    return seller[id][key] || def
}

(7)存儲方法使用

在seller中可以使用兩個方法,添加created

created() {
        this.favorite = loadFromLocal(this.seller.id, KEY, false)
    },
    methods: {
        toggleFavorate() {
            this.favorite = !this.favorite
            saveToLocal(this.seller.id, KEY, this.favorite)
        }
    },

效果:點擊“收藏”後會顯示存儲,切換id=1在收藏local storage中會添加,並且保存狀態。

後續章節傳送

項目總結:vue.js2.5餓了麼APP(7)項目部署與總結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章