【餓了麼】—— Vue2.0高仿餓了麼核心模塊&移動端Web App項目爬坑(三)

前言:接着上一篇項目總結,這一篇是學習過程記錄的最後一篇,這裏會梳理:評論組件、商家組件、優化、打包、相關資料連接。項目github地址:https://github.com/66Web/ljq_eleme,歡迎Star。html


ratings seller
1、評論組件-ratings

       評論組件主要分爲三塊
vue

  • 評分信息-overview
  • 評論選擇-ratingselect
  • 評論詳細信息

       評分信息部分webpack

  • 左側評分
  1. 佈局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>
  2. CSS樣式git

    .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
  3. seller數據:App.vue中的routerview進行傳遞,在rating組件中使用props進行接收,這樣才能夠在模板中直接使用seller.XXX數據
     props: {
        seller: {
          type: Object
        }
      }
  • 右側star組件+商品評分+送達時間
  1. 佈局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>
  2. CSS樣式:es6

            .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
  3. 坑:視口寬度不夠寬時,右側部分過長會出現折行。解決:添加一個mediea Query媒體查詢github

    .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
  1. 同時,已經作好的分割區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">
  2. 要實現滾動,像good組件同樣,須要固定視口的高度,將其定位絕對定位,top爲header組件的高度web

    .ratings
          position: absolute top: 174px
          bottom: 0
          left: 0
          width: 100%
          overflow: hidden

       評論選擇部分 vue-router

  • 使用引用並註冊好的split組件和ratingselect組件
    <split></split>
    <ratingselect @increment="incrementTotal" :select-type="selectType" :only-content="onlyContent" :ratings="ratings">
    </ratingselect>

      評論詳細信息npm

  • 同商品組件,在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,使評論列表部分能夠滾動

  1.   拿到DOM數據,ref="ratings",將better-scroll初始化時機寫在created函數拿到api數據以後
2、商家組件-seller

       基礎操做

  • 接收傳遞進來的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組件,再添加內容,同時不要忘記把圖片拷貝過來
  1. 佈局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']; }
  2. 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
  1. 坑:初始化BScroll語句放在created()中,可是不起做用。
  2. 緣由:seller是異步獲取的,可是咱們的內容都是靠seller裏的數據撐開的,因此一開始內容確定是小於我咱們定義的wrapper的,因此沒有被撐開
  3. 解決:將其放入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(); }
  4. 坑:以前的狀況是切換以後不能滾動,如今的新問題是一開始(沒切換界面以前)就不能滾動了,切換以後就能夠滾動了;
  5. 緣由:created()的執行時機要先於watch中的seller,而後咱們在執行seller中的initScroll的時候就會發現BScroll已經被初始化了,因此initScroll失效,即便在watch中觀察到變化也只能什麼都不作

  6. 解決:必定要爲初始化函數_initScroll()和this._initPics()中的nextTick()下的添加if-else語句,對BScroll進行刷新,完成

  • 商家實景區塊 -- 橫向滾動
  1. 添加圖片,設置樣式,橫向排列
    <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
  2. 原理: pic-wrapper是固定寬度的視口的大小,當裏面的ul超過視口寬度的時候就會出現滾動
  3. 注意:ul是外層的寬度,並非真實的li撐開的寬度
  4. 實現:使用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); },
  • 保存收藏狀態

  1. 解析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}; };
  2. 在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) => { }) }
  3. 刷新以後,收藏樣式就會消失:建立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; }
  4. 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); },
3、優化&打包

       優化

  • 問題:切換界面時會閃現
  • 緣由:界面被從新渲染了,生命週期函數被從新執行了一遍
  • 優化:切換組件的時候,組件以前的狀態也能被保留
  • 解決:vue中提供 vue-router切換組件保留的功能內置組件<keepalive>,在App.vue中更改成
    <keep-alive>
          <router-view :seller="seller"></router-view>
    </keep-alive>

       打包

  • vue-cli 項目打包構建的結果就是根目錄下會多出一個dist文件夾:存儲編譯後的文件
    npm run build
4、相關資料連接

       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-scrollhttp://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


注:項目來自慕課網

相關文章
相關標籤/搜索