說明:本總結來源於慕課網 @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. 概述
商家評價頁面主要是顯示商家評分以及評論,複用了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中會添加,並且保存狀態。
後續章節傳送