【餓了麼】—— Vue2.0高仿餓了麼核心模組&移動端Web App專案爬坑(三)
前言: 接著上一篇專案總結,這一篇 是學習過程記錄的最後一篇,這裡會梳理:評論元件、商家元件、優化、打包、相關資料連結。專案github地址: ofollow,noindex">https://github.com/66Web/ljq_eleme ,歡迎Star。
![]() |
![]() |
ratings | seller |
評論元件主要分為三塊
- 評分資訊-overview
- 評論選擇-ratingselect
- 評論詳細資訊
評分資訊部分
- 左側評分
- 佈局Dom
<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> </div> <split></split>
-
CSS樣式
.overview display flex padding 18px 0 18px 18px .overview-left padding-bottom 6px 0 flex 0 0 137px width 137px // 防止出現相容性問題 border-right 1px solid rgba(7,17,27,0.1) text-align center @media only screen and (max-width 320px) flex 0 0 110px width 110px .score margin-bottom 6px line-height 28px font-size 24px color rgb(255, 153, 0) .title margin-bottom 8px line-height 12px font-size 12px color rgb(7, 17, 27) .rank line-height 10px font-size 10px color rgb(147, 153, 159) .overview-right flex 1 padding 6px 0 6px 24px View Code
- seller資料: App.vue中的routerview進行傳遞,在rating元件中使用props進行接收 ,這樣才可以在模板中直接使用seller.XXX資料
props: { seller: { type: Object } }
- 右側star元件+商品評分+送達時間
- 佈局Dom
<div class="overview"> <div class="overview-left"> ... </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>
-
CSS樣式:
.overview-right flex 1 padding 6px 0 6px 24px @media only screen and (max-width 320px) padding-left 6px .score-wrapper line-height 18px margin-top 8px font-size 0 .title display inline-block vertical-align top line-height 18px font-size 12px color rgb(7, 17, 27) .star display inline-block vertical-align top margin 0 12px .score display inline-block vertical-align top line-height 18px font-size 12px color rgb(255, 153, 0) .delivery-wrapper font-size 0 .title//span文字和文字之間預設是垂直居中的,可以不用加display vertical-align display inline-block vertical-align top line-height 18px font-size 12px color rgb(7, 17, 27) .delivery display inline-block margin-left 12px vertical-align top line-height 18px font-size 12px color rgb(147, 153, 159) View Code
-
坑:視口寬度不夠寬時,右側部分過長會出現折行。解決: 新增一個mediea Query媒體查詢
.overview-left padding-bottom: 6px 0 flex: 0 0 137px width: 137px // 防止出現相容性問題 border-right: 1px solid rgba(7,17,27,0.1) text-align: center @media only screen and (max-width 320px) flex: 0 0 110px width: 110px .overview-right flex 1 padding: 6px 0 6px 24px @media only screen and (max-width 320px) padding-left: 6px
- 頁面很長,需要引用better-scroll
- 同時,已經做好的分割區split元件、星星star元件、評論選擇ratingselect元件、時間戳轉換等也都需要引用
import star from '@/components/star/star' import BScroll from 'better-scroll'; import split from '@/components/split/split' import ratingselect from '@/components/ratingselect/ratingselect' import {formatDate} from '@/common/js/date'
<template> <div class="ratings" ref="ratings"> <!-- ratings-content大於ratings的時候出現滾動 --> <div class="ratings-content">
-
要實現滾動,像good元件一樣, 需要固定視口的高度,將其定位絕對定位,top為header元件的高度
.ratings position: absolute top: 174px bottom: 0 left: 0 width: 100% overflow: hidden
評論選擇部分
- 使用引用並註冊好的split元件和ratingselect元件
<split></split> <ratingselect @increment="incrementTotal" :select-type="selectType" :only-content="onlyContent" :ratings="ratings"> </ratingselect>
-
data中新增ratingselect元件中需要維護的值
data () { return { ratings: [], showFlag: false, selectType: ALL, onlyContent: true }; }
注意:此時模板中的 ratings是所有商品的ratings(針對一個商家) ,不是food.vue中對某一種食品的評價——food.ratings
- 點選切換評價列表,和商品詳情頁類似,可參考 【餓了麼】—— Vue2.0高仿餓了麼核心模組&移動端Web App專案爬坑(二)
評論詳細資訊
- 同商品元件,在created()函式中拿到ratings的API資料,將得到的ratings傳到ratings的元件中
const ERR_OK = 0; created () { this.$http.get('/api/ratings') .then((res) => { res = res.body; if (res.errno === ERR_OK) { this.ratings = res.data; // console.log(this.ratings) this.$nextTick(() => { this.scroll = new BScroll(this.$refs.ratings, { click: true }) }); } } )
-
拿到資料之後在raring元件中填充html中的DOM資料
<div class="rating-wrapper"> <ul> <li v-for="rating in ratings" :key="rating.id" class="rating-item" v-show="needShow(rating.rateType, rating.text)"> <div class="avatar"> <img :src="rating.avatar" width="28px" height="28px"> </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"> <!-- 贊或踩和相關推薦 --> <i class="icon-thumb_up"></i> <span class="item" v-for="item in rating.recommend" :key="item.id">{{item}}</span> </div> <div class="time"> {{rating.rateTime | formatDate}} </div> </div> </li> </ul> </div> View Code
-
CSS樣式
.rating-wrapper padding 0 18px .rating-item display flex padding 18px 0 border-1px(rgba(1, 17, 27, 0.1)) .avatar flex 0 0 28px width 28px margin-right 12px img border-radius 50% .content position relative flex 1 .name margin-bottom 4px line-height 12px font-weight 700 font-size 10px color rgb(7, 17, 27) .star-wrapper margin-bottom 6px font-size 0 .star display inline-block margin-right 16px vertical-align top .delivery display inline-block vertical-align top font-size 10px line-height 12px color rgb(147, 153, 159) .text line-height 18px color rgb(7, 17, 27) font-size 12px margin-bottom 8px .recommend line-height 16px font-size 0 .icon-thumb_up, .item display inline-block margin 0 8px 4px 0 font-size 9px .icon-thumb_up color rgb(0, 160, 220) .item padding 0 6px border 1px solid rgba(7, 17, 27, 0.1) border-radius 1px color rgb(147, 153, 159) background #fffff .time position absolute top 0 right 0 line-height 12px font-size 10px color rgb(147, 153, 159) View Code
-
繫結better-scroll,使評論列表部分可以滾動
- 拿到DOM資料,ref="ratings", 將better-scroll初始化時機寫在created函式拿到api資料之後
基礎操作
- 接收傳遞進來的seller資料
props: { //APP.vue的routerview中已經將seller傳進來了,這裡只需要接收就好 seller: { type: Object } }
-
佈局DOM
<div class="overview"> <h1 class="title">{{seller.name}}</h1> <div class="desc border-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.deliveryTime}}</span>元 </div> </li> </ul> <div class="favorite"@click="toggleFavorite($event)"> <i class="icon-favorite" :class="{'active':favorite}"></i> <!-- 對應是否收藏兩種樣式--> <span>{{favoriteText}}</span> <!-- 有沒有選中對應不同的文字,所以這裡要繫結一個變數,放到data中 --> </div> </div> View Code
-
CSS樣式
.seller position: absolute top: 174px bottom: 0 left: 0 width: 100% overflow: hidden .overview padding: 18px position: relative .title margin-bottom: 8px line-height: 14px color: rgb(7, 17, 27) font-size: 14px .desc padding-bottom: 18px font-size: 0 border-1px(rgba(7, 17, 27, 0.1)) &:before display: none .star display: inline-block vertical-align: top margin-right: 8px .text display: inline-block vertical-align: top margin-right: 12px line-height: 18px // 不能為父元素設定line-heigth,否則元件會被撐高 font-size: 10px color: rgb(77, 85, 93) .remark display: flex padding-top: 18px .block flex: 1 text-align: center border-right: 1px solid rgba(7, 17, 27, 0.1) &:last-child border: none h2 margin-bottom: 4px line-height: 10px font-size: 10px color: rgb(147, 153, 149) .content line-height: 24px font-size: 10px color: rgb(7, 17, 27) .stress font-size: 24px View Code
公告與活動部分
- 先新增一個split元件,再新增內容,同時不要忘記把圖片拷貝過來
-
佈局DOM
<div class="bulletin"> <h1 class="title">公告與活動</h1> <div class="content-wrapper border-1px"> <p class="content">{{seller.bulletin}}</p> </div> <ul v-if="seller.supports" class="supports"> <li class="support-item border-1px" v-for="(item,index) in seller.supports" :key="(item.id,index.id)"> <span class="icon" :class="classMap[seller.supports[index].type]"></span> <span class="text">{{seller.supports[index].description}}</span> </li> </ul> </div> <split></split>
其中: 圖示icon 動態繫結class時, 使用classMap,在created()中定義,通過獲取索引值一一對應 ,同header.vue元件中一樣
created() { this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']; }
- CSS樣式
.bulletin padding: 18px 18px 0 18px .title margin-bottom: 8px line-height: 14px color: rgb(7, 17, 27) font-size: 14px .content-wrapper padding: 0 12px 16px 1px border-1px(rgba(7, 17, 27, 0.1)) .content line-height: 24px font-size: 12px color: rgb(240, 20, 20) .supports .support-item padding: 16px 12px border-1px(rgba(7, 17, 27, 0.1)) font-size 0 &:last-child border-none() .icon display inline-block width 16px height 16px vertical-align top margin-right 6px background-size 16px 16px background-repeat no-repeat &.decrease bg-image('decrease_4') &.discount bg-image('discount_4') &.guarantee bg-image('guarantee_4') &.invoice bg-image('invoice_4') &.special bg-image('special_4') .text display inline-block font-size 12px line-height 16px color rgb(7, 17, 27) View Code
使用BScroll
- 頁面很長,需要引用BScroll
- 坑: 初始化BScroll語句放在created()中 ,但是不起作用。
- 原因:seller是 非同步獲取 的,但是我們的內容都是靠seller裡的資料撐開的,所以一開始內容肯定是小於我我們定義的wrapper的,所以沒有被撐開
- 解決:將其放入watch:{} 中可以監測到seller的變化, 將初始化語句寫成一個方法,在watch中進行呼叫
methods: { _initScroll() { this.$nextTick(() => { if (!this.scroll) { this.scroll = new BScroll(this.$refs.seller, {click: true}); }else{ this.scroll.refresh(); } }) }
watch: { 'seller'() {//觀測seller資料的更新,並且執行更新後的操作 this._initScroll(); this._initPics(); } }, created() { this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']; this._initScroll(); this._initPics(); }
- 坑:之前的情況是切換之後不能滾動,現在的新問題是一開始(沒切換介面之前)就不能滾動了,切換之後就可以滾動了;
-
原因: created()的執行時機要先於watch中的seller ,然後我們在執行seller中的initScroll的時候就會發現BScroll已經被初始化了,所以initScroll失效,即使在watch中觀察到變化也只能什麼都不做
-
解決:一定要為初始化函式_initScroll()和this._initPics()中的nextTick()下的新增 if-else 語句,對BScroll進行重新整理,完成
- 商家實景區塊 -- 橫向滾動
-
新增圖片,設定樣式,橫向排列
<div class="pics"> <h1 class="title">商家實景</h1> <div class="pic-wrapper" ref="picWrapper"> <ul class="pic-list" ref="picList"> <li class="pic-item" v-for="pic in seller.pics" :key="pic.id"> <img :src="pic" width="120" height="90"> </li> </ul> </div> </div> <split></split>
CSS樣式:
.pics padding: 18px .title margin-bottom: 12px line-height: 14px color: rgb(7, 17, 27) font-size: 14px .pic-wrapper width: 100% overflow: hidden white-space: nowrap /*不產生折行*/ .pic-list font-size: 0 .pic-item display: inline-block margin-right: 6px width: 120px height: 90px &:last-child margin: 0 View Code
- 原理: pic-wrapper是 固定寬度的視口的大小 ,當裡面的 ul超過視口寬度的時候就會出現滾動
- 注意:ul是外層的寬度,並不是真實的li撐開的寬度
- 實現:使用BScroll實現滾動,新增_initPic()方法,並把它新增到watch和create()中
_initPics() { if(this.seller.pics) { let picWidth = 120; let margin = 6; let width = (picWidth + margin)*this.seller.pics.length - margin;//計算ul的寬度 this.$nextTick(() => { this.$refs.picList.style.width = width + 'px';//設定ul寬度,不要忘記單位 if (!this.picScroll) { this.picScroll = new BScroll(this.$refs.picWrapper, { scrollX: true,//表示橫向滾動 eventPassthrough:'vertical'//橫向滾動圖片的時候忽略縱向的滾動 }); }else{ this.scroll.refresh(); } }) } }
收藏商家
- 收藏按鈕:設定:active樣式(紅,白)和字型的變化(收藏和未收藏)
<div class="favorite"@click="toggleFavorite($event)"> <i class="icon-favorite" :class="{'active':favorite}"></i> <!-- 對應是否收藏兩種樣式--> <span>{{favoriteText}}</span> <!-- 有沒有選中對應不同的文字,所以這裡要繫結一個變數,放到data中 --> </div>
- favorite是一個變數,在data裡觀測,使用computed定義favoriteText()改變並返回變數
data() { return { // favorite: false, //預設沒有被收藏,從localStorge中取讀取,不是一個預設值了 favorite: (() => { return loadFromlLocal(this.seller.id, 'favorite', false); })() }; }, computed: { favoriteText() { return this.favorite ? '已收藏' : '收藏'; } }
-
CSS樣式
.favorite position: absolute right: 11px top: 18px width: 50px text-align: center .icon-favorite display: block margin-bottom: 4px line-height: 24px font-size: 24px width: 50px color: #d4d6d9 &.active color: rgb(240,20,20) .text line-height: 10px font-size: 10px color: rgb(77,85,93) View Code
-
新增點選事件,methods中定義toggleFavorite()方法
toggleFavorite(event) { if (!event._constructed) { return; } this.favorite = !this.favorite; //這樣寫取法區分商家id,不同商家的狀態一樣 //localStorage.favorite = this.favorite; saveToLocal(this.seller.id, 'favorite', this.favorite); },
-
儲存收藏狀態
- 解析url中商家id資料為Object物件 :每一個商家都有一個唯一的id,這個id存在url中,所以建立util.js,封裝一個函式,將url解析成物件的模式
/** * 解析url引數 * Created by yi on 2016-12-28. * @return Object {id:12334} */ export function urlParse() { let url = window.location.search; let obj = {}; let reg = /[?&][^?&]+=[^?&]]+/g; let arr = url.match(reg); // ['?id=123454','&a=b'] if (arr) { arr.forEach((item) => { let tempArr = item.substring(1).split('=');// 先分割取到id=123454,之後用=號分開 let key = tempArr[0]; let val = tempArr[1]; obj[key] = val; }); } // return obj; return {id: 123123}; };
- 在App.vue元件中引入urlParse,並在data中 獲取data ,通過擴充套件物件在data.json檔案中 存入data
import {urlParse} from './common/js/util.js' data() { return { seller:{ id: (() => { let queryParam = urlParse(); //console.log(queryParam) return queryParam.id; })() } } }, created: function() { this.$http.get('/api/seller?id=' + this.seller.id) .then((res) => { res = res.body; if (res.errno === ERR_OK) { this.seller = res.data; // console.log(this.seller) this.seller = Object.assign({}, this.seller, res.data);//擴充套件物件 新增其它屬性--id } }, (err) => { }) }
-
重新整理之後,收藏樣式就會消失:建立store.js實現資料的存取,專門 存取不同商家的id,通過唯一id,將收藏的資訊新增到localStorge中
//savaToLocal(this.seller.id, 'favorite', this.favorite);存取 export function saveToLocal(id, key, value) { //儲存到localStorge let seller = window.localStorage.__seller__; if (!seller) { //沒有seller的時候,初始化,定義一個seller物件,並給他設定一個id seller = {}; seller[id] = {}; // 每個id下都是一個單獨的obj } else { seller = JSON.parse(seller); // JSON 字串轉換為物件 if (!seller[id]) { //判斷是否有當前這個商家 seller[id] = {}; } } seller[id][key] = value; // 將key和value存到id這個物件的下邊 //將一個JavaScript值(物件或者陣列)轉換為一個 JSON字串 window.localStorage.__seller__ = JSON.stringify(seller); } //loadFromlLocal(this.seller.id, 'favorite', false);讀取 export function loadFromlLocal(id, key, def) { //讀取,讀不到的時候傳入一個default變數 let seller = window.localStorage.__seller__; if (!seller) { return def; } seller = JSON.parse(seller)[id]; // 取到這個商家下所有的物件 if (!seller) { return def; } let ret = seller[key]; return ret || def; }
-
seller.vue中引入,並在data和toggleFavorite()中使用這兩個方法:
import {saveToLocal, loadFromlLocal} from 'common/js/store.js';
data() { return { // favorite: false, //預設沒有被收藏,從localStorge中取讀取,不是一個預設值了 favorite: (() => { return loadFromlLocal(this.seller.id, 'favorite', false); })() }; }
toggleFavorite(event) { if (!event._constructed) { return; } this.favorite = !this.favorite; //這樣寫取法區分商家id,不同商家的狀態一樣 //localStorage.favorite = this.favorite; saveToLocal(this.seller.id, 'favorite', this.favorite); },
優化
- 問題:切換介面時會閃現
- 原因:介面被重新渲染了,生命週期函式被重新執行了一遍
- 優化:切換元件的時候,元件之前的狀態也能被保留
- 解決:vue中提供 vue-router切換元件保留的功能內建元件<keepalive>,在App.vue中更改為
<keep-alive> <router-view :seller="seller"></router-view> </keep-alive>
打包
- vue-cli 專案打包構建的結果就是根目錄下會多出一個dist資料夾:儲存編譯後的檔案
npm run build
Vue.js官網 : https://vuejs.org.cn/
Vue-cli : https://github.com/vuejs/vue-cli
Vue-resource : https://github.com/vuejs/vue-resource
Vue-router : https://github.com/vuejs/vue-router
better-scroll : http://npm.taobao.org/package/better-scroll
webpack官網 : https://www.webpackjs.com/
Stylus中文文件 : https://www.zhangxinxu.com/jq/stylus/
es6入門學習 : http://es6.ruanyifeng.com/
eslint規則 : http://eslint.org/docs/rules/
裝置畫素比 : https://www.zhangxinxu.com/wordpress/2012/08/window-devicepixelratio/
Flex佈局 : http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
貝塞爾曲線測試 : http://cubic-bezier.com/#.17,.67,.83,.67
版權宣告:本文原創,未經本人允許不得轉載