header 組件

起步css

header 組件需完成:html

1.頭部商家信息的展現前端

2.完成公告部分vue

3.彈出層的實現webpack

在這個過程當中,很重要的一步是經過異步請求後端數據接口,接收並渲染相關商家數據。ios

如何實現:使用第三方插件 vue-resource,經過在父組件(App.vue)發送 Ajax 請求得到後端數據,而後經過 header 的 Prop 屬性將數據傳遞給 header 組件。css3

 

 

知識準備git

vue-resource 簡介github

1)vue-resource 定位
vue-resource 是 Vue.js 的一款插件,它能夠經過 XMLHttpRequest 或 JSONP 發起請求並處理響應。也就是說,它能夠用於處理先後端數據請求、數據交互,達到與 $.ajax 同等功能。web

2)vue-resource 特色:

2-1)支持 Promise API 和 URI Templates

Promise是ES6的特性,Promise的中文含義爲「先知」,Promise對象用於異步計算。URI Templates表示URI模板,有些相似於ASP.NET MVC的路由模板。

2-2)支持攔截器(https://github.com/pagekit/vue-resource/blob/develop/docs/http.md#interceptors

攔截器是全局的,攔截器能夠在請求發送前和發送請求後作一些處理。攔截器在一些場景下會很是有用,好比請求發送前在headers中設置access_token,或者在請求失敗時,提供共通的處理方式。

2-3)支持主流的瀏覽器:和Vue.js同樣,vue-resource除了不支持IE 9如下的瀏覽器,其餘主流的瀏覽器都支持。

2-4)支持 vue 1.0版本 及 vue 2.0版本

2-5)體積小:vue-resource很是小巧,在壓縮之後只有大約14KB,服務端啓用gzip壓縮後只有5.3KB大小,這遠比jQuery的體積要小得多。

PS:

1.支持 promise api 的形式,就是經過同步鏈式的寫法來支持異步的操做。

2.關於 vue-resource 更多知識點:http://www.cnblogs.com/keepfool/p/5657065.html

 

組件

1.什麼是組件?
組件 (Component) 是 Vue.js 最強大的功能之一。組件能夠擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素,Vue.js 的編譯器爲它添加特殊功能。在有些狀況下,組件也能夠表現爲用 is 特性進行了擴展的原生 HTML 元素。

全部的 Vue 組件同時也都是 Vue 的實例,因此可接受相同的選項對象 (除了一些根級特有的選項) 並提供相同的生命週期鉤子。

2.使用組件

1)data 必須是函數

2)組件組合

組件設計初衷就是要配合使用的,最多見的就是造成父子組件的關係:組件 A 在它的模板中使用了組件 B。它們之間必然須要相互通訊:父組件可能要給子組件下發數據,子組件則可能要將它內部發生的事情告知父組件。然而,經過一個良好定義的接口來儘量將父子組件解耦也是很重要的。這保證了每一個組件的代碼能夠在相對隔離的環境中書寫和理解,從而提升了其可維護性和複用性。

在 Vue 中,父子組件的關係能夠總結爲 prop 向下傳遞,事件向上傳遞。父組件經過 prop 給子組件下發數據,子組件經過事件給父組件發送消息。看看它們是怎麼工做的。

3.Prop

1)使用 Prop 傳遞數據

組件實例的做用域是孤立的。這意味着不能 (也不該該) 在子組件的模板內直接引用父組件的數據。父組件的數據須要經過 prop 才能下發到子組件中。

子組件要顯式地用 props 選項聲明它預期的數據:

Vue.component('child', {
// 聲明 props
props: ['message'], // 就像 data 同樣,prop 也能夠在模板中使用 // 一樣也能夠在 vm 實例中經過 this.message 來使用 template: '<span>{{ message }}</span>' })

而後咱們能夠這樣向它傳入一個普通字符串:

<child message="hello!"></child>

結果:

hello!

2)動態 Prop

與綁定到任何普通的 HTML 特性相相似,咱們能夠用 v-bind 來動態地將 prop 綁定到父組件的數據。每當父組件的數據變化時,該變化也會傳導給子組件:

<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
<!-- v-bind 的縮寫語法 -->
<!-- <child :my-message="parentMsg"></child> -->

若是你想把一個對象的全部屬性做爲 prop 進行傳遞,可使用不帶任何參數的 v-bind (即用 v-bind 而不是 v-bind:prop-name)。例如,已知一個 todo 對象:

todo: {
text: 'Learn Vue', isComplete: false }

而後:

<todo-item v-bind="todo"></todo-item>

將等價於:

<todo-item
    v-bind:text="todo.text"
    v-bind:is-complete="todo.isComplete"
></todo-item>

 

實例生命週期鉤子

每一個 Vue 實例在被建立時都要通過一系列的初始化過程——例如,須要設置數據監聽、編譯模板、將實例掛載到 DOM 並在數據變化時更新 DOM 等。同時在這個過程當中也會運行一些叫作生命週期鉤子的函數,這給了用戶在不一樣階段添加本身的代碼的機會。

好比 created 鉤子能夠用來在一個實例被建立以後執行代碼:

new Vue({
  data: {
    a: 1 }, created: function () { // `this` 指向 vm 實例 console.log('a is: ' + this.a) } }) // => "a is: 1"

也有一些其它的鉤子,在實例生命週期的不一樣階段被調用,如 mounted、updated 和 destroyed。生命週期鉤子的 this 上下文指向調用它的 Vue 實例。

 

 

開發

header組件內容

以下圖,包括外層組件以及彈出層。在對這兩塊進行詳細設置以前,須要先經過異步請求後端數據接口,接收並渲染相關商家數據。

因此本節有如下三部分知識點:

1.獲取數據

2.外層組件

3.彈出層

 

 PS:本節全部代碼在文章底部。

 

 

1、獲取數據

獲取後臺商家數據,得到 seller對象數據

1.安裝第三方插件 vue-resource,在 main.js 中引用而且註冊該插件。

安裝方法:npm install vue-resource --save

// 引用
import Vueresource from 'vue-resource'; 
// 全局註冊
Vue.use(Vueresource);

 

2.在 App.vue 中定義一個 seller 對象:使用 data() 方法,在函數內返回一個空對象 seller。

Q:爲何是用 data() 方法而不是 data() 對象?

A:由於在 vue.js 中,組件是能夠被複用的,也就是說,若是使用對象而不是函數的話,某一組件的修改會影響另一個組件,因此這裏定義的是方法而非函數。更多知識可看官網:data 必須是函數 。

 

3.在 APP.vue 中發送 ajax 請求獲取後臺商家數據(seller 對象),而後塞給上一步驟的空對象就能夠了。

1)何時去發送這樣一個 ajax 請求?vue實例化的時候都有一個生命週期,其中有個鉤子叫 created() ,vue實例被生成後就調用這個函數。通常能夠在created函數中調用ajax獲取頁面初始化所需的數據。一個vue實例被生成後還要綁定到某個html元素上,以後還要進行編譯,而後再插入到document中。每個階段都會有一個鉤子函數,方便開發者在不一樣階段處理不一樣邏輯。

2)在 created 裏面經過 get() 方法去獲取數據,獲取地址是「 /api/seller 」,請求發送之後,調用 then() 方法。then() 方法中的第一個參數是成功的回調,第二個參數是失敗的回調。由於這裏是模擬後臺數據,因此必定是成功的,因此只寫成功的回調方法。response 是一個方法,返回的內容不是json對象,而是屬性。看 文檔 能夠知道,body參數返回的數據類型是 object 類型,正是咱們所須要的。

3)接着判斷 errno 是否爲 0,爲 0 表示正常,而後返回數據。關於 errno 的設置在 webpack.dev.conf.js 中能夠找到。這裏定義一個常量 ERR_OK 爲 0,是爲了閱讀代碼時更加清晰,知道 0 表明什麼,無需通篇查找 errno = 0 的含義,代碼更爲友好。

 1 const ERR_OK = 0;
 2   //  註冊
 3 export default {
 4   created() {
 5     this.$http.get('/api/seller').then(response => {
 6       // get body data
 7       response = response.body;
 8       console.log(response);
 9       if (response.errno === ERR_OK) {
10         this.seller = response.data;
11         console.log(response.data);
12       }
13     });
14   }
15   ...
16 };

 PS:在 network 能夠看到有 seller 的數據,在 console 能夠看到返回的數據是 object 類型,同時也能夠看到這些數據加上了不少 get 和 set 方法。這是 vue 自動給這些字段添加的方法,用於監聽這些字段的行爲。若是對象某些值被修改時,這些變化就能被監聽到,而且及時映射到 DOM 上。關於 get 和 set 方法,在導語1——vue簡單介紹的數據響應原理知識點處有說起到。

  

 

接下來進行外層組件和彈出層的設計。這兩部分都須要動態獲取數據,須要將 seller 對象傳遞給 header 組件,讓 header 組件將其渲染出來。能夠經過 v-bind: 將其進行數據綁定,v-bind: 縮寫爲 : 。

先看一下代碼框架:

content-wrapper:外層組件中的上層部分

bulletin-wrapper:外層組件中的公告欄部分

background:header 組件的背景層

detail:彈出層

 

2、外層組件

下圖能夠看出,外層組件大致分爲上下兩個部分:上層又可分爲左右兩部分,左邊是圖片,右邊是商家相關信息;下層是公告欄相關信息。除此以外,還有一個背景層。

 

1.上層部分

 

 1     <div class="content-wrapper">
 2       <div class="avatar">
 3         <img :src="seller.avatar" alt="" width="64" height="64">
 4       </div>
 5       <div class="content">
 6         <div class="title">
 7           <span class="brand"></span>
 8           <span class="name">{{seller.name}}</span>
 9         </div>
10         <div class="description">
11           {{seller.description}}/{{seller.deliveryTime}}分鐘送達
12         </div>
13         <div class="support" v-if="seller.supports">
14           <span class="icon" :class="classMap[seller.supports[0].type]"></span>
15           <span class="text">{{seller.supports[0].description}}</span>
16         </div>
17       </div>
18       <div v-if="seller.supports" class="support-count" v-on:click="showDetail">
19         <span class="count">{{seller.supports.length}}個</span>
20         <i class="icon-keyboard_arrow_right"></i>
21       </div>
22     </div>

1)Q:引用頭像圖片地址,是 :src 而不是 src的緣由

A:selller.avatar 一開始是不存在的,由於 seller 是空對象,其數據是經過 ajax 請求異步獲取數據而後填充得來的。因此若是直接用 src ,編譯就會直接解析 src,此時的 seller 仍是是空的,天然就找不到 selller.avatar,就會報錯。

 

2)Q:爲何要用 v-if 判斷存不存在 supports ?

A:由於這是由數據決定的,有可能該數組不存在。

 

3)Q:不加 v-if 會怎麼樣?
A:在這裏的話,不加 v-if 也能正確顯示信息。可是控制檯會報錯。這是由於在 App.vue 的 html 代碼中,咱們將 seller 對象傳遞給了 header 組件,而在下邊 JavaScript 代碼中, seller 對象一開始被設置爲空對象,其數據是經過 ajax 請求異步獲取數據而後填充得來的。因此此時 header 組件接收的只是一個空的 seller 對象,原本就是 undefined 。編譯器此時解析的話,天然找不到 seller 對象的相關數據,天然就報錯了。

 

4)support 設置

如圖所示,這一塊的設置須要根據後端返回的數據,進行判斷後動態的顯示。由於有可能今天是"在線支付滿28減5"排第一位,明天是"單人精彩套餐"排第一位,程序須要根據數據準確顯示出第一位的內容。

問題:data.json 的 supports 數組只有type 和 description 屬性,怎麼才能正確取得圖片呢?

設計思路:每個 type 對應到前端中不一樣的 class,而後給不一樣的 class 添加不一樣的圖片就能夠了。因此能夠給 .support 動態綁定一個class,即根據後端返回的 type(第一位),肯定到底綁定哪個 class(例如減對應的 class 爲 decrease),而後將該 class 傳遞給動態綁定的class(:class),這樣就能正確顯示。

如何實現:

第一步:在組件 created() 中,定義一個 classMap,將 type 與 class 進行綁定(在 data.json 文件中,supports 數組中 type 順序:減,折,套餐,發票,保障,則對應class名稱:decrease,discount,special,invoice,guarantee);

第二步:給不一樣的 class 設置不一樣的圖片;

第三步:將數組 classMap 綁定到 class 上,即 :class="classMap[seller.supports[0].type]。

以下圖,html 中的 classMap 能夠訪問到this.classMap,進而訪問到數組裏的 class,每個 class 又有真正對應的值。這樣就能正確顯示出圖片。

檢驗成果:能夠在data.json中將type=2的那一塊數據調到頂端,從新編譯刷新能夠發現網頁的數據發生了變化。

 

5)圖標字體的使用:<i class="icon-keyboard_arrow_right"></i>

在 icon.styl 文件中能夠找到類名。

 

6)浮層效果的入口:絕對定位,且定位在右下角,這個定位是相對於content-wrapper

 

7)CSS設置

7-1)在 base.styl 文件中,定義通用樣式。

7-2)左右兩塊區域是並排,能夠直接設置爲 inline-block。

7-3)左右兩塊區域中間有空格,這是由於空白字符(換行符、空格符等)的影響,爲消除該影響,能夠設置 font-size 爲 0。

7-4)根據不一樣 dpr 進行不一樣圖片的顯示,在 mixin.styl 中設置該函數。

1   bg-image($url)
2   // 正常狀況下
3   background-image url($url + "@2x.png")
4   // 最小 dpr=3 的狀況下
5   @madia (-webkit-min-device-pixel-ratio 3) (-min-device-pixel-ratio 3)
6     background-image url($url + "@3x.png")

7-5)不能像加載 js 庫同樣,進行省略別名的使用。由於那是針對 import js 庫,css 這邊是不能夠的,因此這裏只能寫相對路徑。

@import '../../common/stylus/mixin'

7-6)Chrome 瀏覽器默認最小字體是 12px,因此設置爲 10px 沒辦法生效,能夠在手機端看效果。

 

8)圖片地址
在控制檯能夠看到,圖片在編譯過程被webpack自動轉換成 base64 編碼格式,而後導入代碼了,因此這裏是 base64 的地址。

 

 

 2.公告欄部分

公告欄拆分:左邊圖片,中間文字,右邊圖標字體(箭頭),點擊箭頭能夠展開浮層。

1 <div class="bulletin-wrapper" v-on:click="showDetail">
2   <span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
3   <i class="icon-keyboard_arrow_right"></i>
4 </div>

1)清除空白字符的影響

方法1:設置 font-size 爲 0 。

方法2:將兩個 span 寫在一塊兒,不要換行。

這裏選第二個方法,由於後邊要進行省略號的設置,若是選第一個方法,會看不見省略號。

 

2)省略號設置

1 /*要爲文字限定顯示範圍,位置應該限定到箭頭前面*/
2 padding 0 22px 0 12px
3 /*文字超過寬度省略的設置*/
4 white-space nowrap
5 overflow hidden
6 text-overflow ellipsis

要進行省略號設置,必須先限定文字顯示範圍,因此這裏設置 padding 時,是設置到省略號前面,表明從省略號到最後那段距離都不能顯示文字。 

後面三行代碼是爲了設置省略號:white-space nowrap(不自動換行)、overflow hidden(超過範圍隱藏)、text-overflow ellipsis(省略號設置)。

 

3)圖標字體(箭頭),即浮層效果的入口:絕對定位,這個定位是相對於父元素 bulletin-wrapper 。

 

4)點擊效果的實現

給元素添加點擊事件 v-on:click="showDetail" ,在 method() 方法裏定義點擊事件。

detailShow 是彈出層設置的用於控制彈出層顯示隱藏的一個變量,detailShow 值爲 true 時顯示,爲 false 時隱藏。更多知識在彈出層進行講解。

 

5)CSS設置

左邊圖片和中間文字不是居中對齊,這是由於兩部分的高度不一樣,因此設置 vertical-align top,讓兩部分頂部對齊。此時發現左邊圖片在上方,能夠設置文字的 margin-top:(28-12)/2=8px (父元素高28px,文字12px),便可居中對齊。

 

3.背景層 

  

能夠看到,背景是虛化的圖片。

1)給背景層設置絕對定位,這個定位是相對於父元素 header 。

2)設置 z-index 爲 -1,讓該背景層處於最下方,不要遮蓋掉其餘設置。

3)寬高撐滿整個背景層。

4)使用 filter 濾鏡產生模糊效果

設置完在瀏覽器查看時,會發現背景滲透到公告下,這是由於設置了blur。能夠在父元素 header 上添加 overflow:hidden ,便可解決該問題。

 

  

3、彈出層

浮層高度大於手機時,應該有下拉功能。無論浮層中內容多少,關閉按鈕都必須固定位於下方,這裏將用到 css sticky fotter 佈局來實現該功能。該佈局通常會有 detail-wrapper 和 detail-close(關閉按鈕) 兩個層,而真正內容寫在 detail-main 中:

1   <div class="detail-wrapper clearfix">
2     <div class="detail-main"></div>
3   </div>
4   <div class="detail-close"></div>

 下面將首先對彈出層的代碼框架佈局進行詳細講解,而後再把彈出層分紅彈出層內容還有關閉按鈕兩部分進行講解。

 

1.代碼框架佈局

如下是彈出層的代碼框架:

 

1)顯示隱藏設置(v-show

彈出層是應當實現顯示和隱藏功能,這裏可使用 vue 的條件渲染 v-show 來實現。設置一個 detailShow 變量,經過改變這個變量的值(true/false),來控制彈出層顯示或隱藏。

如何實現:給vue實例中添加一個選項data(),這個 data() 是一個function(這是以前說過的組件複用問題),該方法會返回一個 object,object 裏面的變量就是它所須要去跟蹤依賴的一些變量。也就是說,vue在實例化時,會對 data 對象中的變量去遍歷,而後添加 getter 和 setter 方法,這樣,當變量變化時,DOM 會根據變量的變化而變化,因此這裏用v-show控制detailShow。

使用 v-show 的好處:不須要額外寫 DOM 代碼,都是直接操做變量便可。

 

 

PS:代碼中有兩處須要涉及到 detailShow 的設置,爲這兩個層添加 click 事件。

原理:在方法裏面改變 detailShow 的值,由於 detailShow 是依賴跟蹤的,當點擊以後調用該方法,this.detailShow 會依賴跟蹤到 data 裏面的 detailShow,並改變其值,由於是依賴跟蹤,vue 能夠檢測到其變化,並將該變化映射到 DOM 上,這樣就能夠經過改變數據的值來實現彈層的懸浮或者隱藏。

 

2)過渡漸變(transition

彈出層彈出的時候,背景顏色應該由淺到深,彈出層隱藏的時候,背景顏色應該由深到淺。這種漸變的設置能夠用 vue 的內置組件 transition 實現。如下是基本的 HTML 結構:

transition 有一個 name 屬性,添加該屬性後,會自動生成 CSS 過渡類名。例如:name: 'fade' 將自動拓展爲.fade-enter,.fade-enter-active等。在這裏設置背景顏色後,再設置過渡的類名 .fade-enter,.fade-enter-active,.fade-leave-to,.fade-leave-active 的樣式便可。

可在瀏覽器中查看效果:

 

3)CSS設置

3-1)彈出層相對於窗口應該是 fixed 結構。

3-2)彈出層應該位於全部層的最上方,因此父元素設置 z-index 爲 100 。

3-3)當內容超過屏幕高度,彈出層應該產生滾動條,因此設置 overflow 屬性。

3-4)背景文字虛化設置 backdrop-filter: blur(10px),這個設置支持 ios 設備,能夠在 ios 手機上查看效果。能夠查看 backdrop-filter 的使用範圍。

 

2.彈出層內容

1)CSS設置

1-1)要給 .detail-wrapper 層加上清除浮動 clearfix ,樣式設置寫在 base.styl 。

1-2).detail-wrapper 的最小高度應該跟視口高度同樣,因此設置爲 100%。

1-3).detail-main 中設置 padding-bottom 64px。若是沒有 padding-bottom,.detail-main 的內容會蓋住關閉按鈕。加了後會撐開高度,這部分高度會給按鈕留住位置。

 

2)星級組件設置(star.vue)

星級評價這一塊內容在以後多個頁面會使用到,因此將其抽象成 star 組件。

在這裏設置爲總共五顆星,評分範圍爲 0-5,評分規則是向下取0.5倍數的值,好比4到4.4會變成4(顯示四顆全星,一顆無星),4.5到4.9會變成4.5(顯示四顆全星和一顆半星)。此處有一個思路,就是根據不一樣分數範圍製做不一樣的圖片,而後引用圖片,以下圖。

可是爲了增長代碼的可用性,最好不要這樣作,咱們能夠經過分數來計算到底有多少顆全星和無星,以及是否有半星。

先建立相應的文件,在 star.vue 中編寫代碼。

實現代碼以下:

2-1)HTML 代碼

1 <template>
2   <div class="star" :class="starType">
3     <span v-for="itemClass in itemClasses" :class="itemClass" class="star-item"></span>
4   </div>
5 </template>

先給星級組件添加父元素,類名是 star,將星級組件包裹起來,並 v-bind 方式定義一個 starType 。再添加評星,用 v-for 指令遍歷 itemClasses 數組,這個數組是最後全部星星存放的數組,遍歷該數組,獲得不一樣的 itemClass,再經過 CSS 設置不一樣 itemClass ,最終就能夠獲得不一樣的星級評定。

2-2)JavaScript 代碼

 1 <script type="text/ecmascript-6">
 2   const LENGTH = 5;
 3   const CLS_ON = 'on';
 4   const CLS_HALF = 'half';
 5   const CLS_OFF = 'off';
 6 
 7   export default {
 8     props: {
 9       size: {
10         type: Number
11       },
12       score: {         // 評價的分數
13         type: Number
14       }
15     },
16     computed: {
17       starType() {
18         return 'star-' + this.size;
19       },
20       itemClasses() {
21         let result = []; // 最終全部星星存放的數組
22         let score = Math.floor(this.score * 2) / 2;
23         let hasDecimal = score % 1 !== 0;   // 小數
24         let integer = Math.floor(score);    // 整數
25         for (let i = 0; i < integer; i++) { // 全星
26           result.push(CLS_ON);
27         }
28         if (hasDecimal) { // 是否有半星(半星最多有一個)
29           result.push(CLS_HALF);
30         }
31         while (result.length < LENGTH) {    // 無星
32           result.push(CLS_OFF);
33         }
34         return result;
35       }
36     }
37   };
38 </script>

如下是部分 CSS 代碼:

 1 .star
 2     ...
 3     &.star-48
 4       .star-item
 5         ...
 6         &.on
 7           bg-image('star48_on')
 8         &.half
 9           bg-image('star48_half')
10         &.off
11           bg-image('star48_off')
12     &.star-36
13      ...
14     &.star-24
15      ...

2-2-1)先經過 props 去接收外部傳來的兩個參數:size 和 score,這兩個參數都是 Number 類型。size 的值是 24,36,48 中的一個,是圖片的尺寸類型,score是評分。

2-2-2)starType 是經過 size 參數計算來的,能夠經過 conputed 計算屬性計算出來,由於參數 size 自己會有 getter 和 setter 方法依賴跟蹤。經過計算屬性拼接出starType 的值,從而產生不一樣的 starType,即不一樣的 class,接下來對 class(star-48,star-36,star-24)進行樣式設計便可。其中,on、half、off 分別表明全星、半星、無星。

2-2-3)itemClasses 是數組,要根據 score 進行計算,數組內容其實就是on、half,off 的組合,經過這些類組成星級評定。好比三顆半星,那麼 itemClasses 應該是有三個 on,一個 half,一個 off。依舊在 conputed 計算屬性裏寫代碼,由於 itemClasses 是數組,因此先定義一個 result 數組,而後進行 score 的計算轉化。在轉換後的 score 的基礎上,對其求餘,有餘數表明有一顆半星(half),對 score 進行取整,該整數就是全星(on)的個數。注意:半星最多隻有一個。最後就是將 on 和 half 寫進 result 數組,判斷 result 長度是否爲 5,不爲 5 的話,要補上 off。

PS:能夠看到一開始設置了四個常量 LENGTH、CLS_ON、CLS_HALF、CLS_OFF,這是爲了增長代碼的可用性。

2-2-4)在 header.vue 中經過 components 註冊 star 組件並引用。

<star :size="48" :score="seller.score"></star>
   components: {
      star
    }

 

3)小標題的設置

小標題的設置,從樣式上看,應該是左右兩邊的線有一個自適應的能力,可根據屏幕大小延伸,標題居中,標題與線之間還有一部分空白。此處用到了一個經典佈局,即 flex 佈局 。下面是 HTML代碼,從代碼中能夠看到,這裏用了三個 div 元素而不是 span 元素,這是由於用 span 的話,在一些 Android 瀏覽器上會產生一些間距問題,所以這裏用 div 。

1 <div class="title">
2   <div class="line"></div>
3   <div class="text">優惠信息</div>
4   <div class="line"></div>
5 </div>

如下是部分 CSS 代碼,從代碼中能夠看到咱們只寫了一句 display flex,並無寫其它兼容性代碼。這是由於編譯時,vue-loader 中有 postcss 工具,能夠自動添加有兼容性代碼樣式。postcss 是根據 can i use 官網寫的代碼。

 1 .title
 2   display flex
 3   width 80%
 4   margin 28px auto 24px auto
 5   .line
 6     flex 1
 7     position relative
 8     top -6px
 9     border-bottom 1px solid rgba(255, 255, 255, 0.2)
10   .text
11     padding 0 12px
12     font-weight 700
13     font-size 14px

 

PS:關於 flex 佈局(只挑本節涉及到的知識點進行講解)

1. flex-grow屬性:定義項目的放大比例,默認爲0,即若是存在剩餘空間,也不放大。

.item {
    flex-grow: <number>; /* default 0 */
}

若是全部項目的flex-grow屬性都爲1,則它們將等分剩餘空間(若是有的話)。若是一個項目的flex-grow屬性爲2,其餘項目都爲1,則前者佔據的剩餘空間將比其餘項多一倍。

2. flex-shrink屬性:定義了項目的縮小比例,默認爲1,即若是空間不足,該項目將縮小。

.item {
    flex-shrink: <number>; /* default 1 */
}

若是全部項目的flex-shrink屬性都爲1,當空間不足時,都將等比例縮小。若是一個項目的flex-shrink屬性爲0,其餘項目都爲1,則空間不足時,前者不縮小。負值對該屬性無效。

3. flex-basis屬性:定義了在分配多餘空間以前,項目佔據的主軸空間(main size)。瀏覽器根據這個屬性,計算主軸是否有多餘空間。它的默認值爲auto,即項目的原本大小。

.item {
    flex-basis: <length> | auto; /* default auto */
}

它能夠設爲跟width或height屬性同樣的值(好比350px),則項目將佔據固定空間。

4.flex屬性:是 flex-grow, flex-shrink 和 flex-basis 的簡寫,默認值爲 0 1 auto。後兩個屬性可選。

.item {
    flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

該屬性有兩個快捷值:auto (1 1 auto) 和 none (0 0 auto)。

建議優先使用這個屬性,而不是單獨寫三個分離的屬性,由於瀏覽器會推算相關值。

 

4)優惠信息的設置

使用無序列表元素 ul 和 li,用 v-for 遍歷 data.json 文件中的 supports 數組,而後展現出來。由於 v-for 支持一個可選的第二個參數爲當前項的索引,因此咱們寫大量額外的代碼,只須要在 v-for 遍歷時,加上 index,下邊就能夠能夠很容易的取到正確的圖片和文字了(classMap是五個圖片類名的數組)。更多關於 v-for的知識,能夠查看:列表渲染 以及 v-for-遍歷數組時的參數順序-變動 。

1 <ul v-if="seller.supports" class="supports">
2   <li class="support-item" v-for="(item, index) in seller.supports">
3     <span class="icon" :class="classMap[seller.supports[index].type]"></span>
4     <span class="text">{{seller.supports[index].description}}</span>
5   </li>
6 </ul>

 

3.關閉按鈕

1)CSS設置

margin -64px auto 0 auto:由於 .detail-wrapper 與 .detail-close 同級,而當 .detail-wrapper 滿屏時,.detail-close 是不會搶佔底部的空間,因此須要設置一個向上的margin,這樣才能一直出如今屏幕的最底部。

2)添加關閉按鈕功能

在 HTML 代碼中加上 click 事件,注意 v-on:click 能夠縮寫爲 @click 。

1 <div class="detail-close" v-on:click="hideDetail">
2   <i class="icon-close"></i>
3 </div>

在 JavaScript 代碼中將 detailShow 的值設置爲 false(彈出層隱藏)。

1 methods: {
2   hideDetail() {
3     this.detailShow = false;
4   }
5 },

 

至此,header組件已所有完成。

附代碼:

 1 <template>
 2   <div id="app">
 3     <v-header :seller="seller"></v-header>
 4     <div class="tab border-1px">
 5       <div class="tab-item">
 6         <!-- 使用 router-link 組件來導航. -->
 7         <!-- 經過傳入 `to` 屬性指定連接. -->
 8         <!-- <router-link> 默認會被渲染成一個 `<a>` 標籤 -->
 9         <router-link to="/goods">商品</router-link>
10       </div>
11       <div class="tab-item">
12         <router-link to="/ratings">評論</router-link>
13       </div>
14       <div class="tab-item">
15         <router-link to="/seller">商家</router-link>
16       </div>
17     </div>
18     <!--內容區-->
19     <div class="content">
20       <!-- 路由出口 -->
21       <!-- 路由匹配到的組件將渲染在這裏 -->
22       <router-view></router-view>
23     </div>
24   </div>
25 
26 
27 </template>
28 
29 <script type="text/ecmascript-6">
30   //  引用
31   import header from 'components/header/header.vue';
32 
33   const ERR_OK = 0;
34   //  註冊
35   export default {
36     data() {
37       return {
38         seller: {}
39       };
40     },
41     created() {
42       this.$http.get('/api/seller').then(response => {
43         // get body data
44         response = response.body;
45         console.log(response);
46         if (response.errno === ERR_OK) {
47           this.seller = response.data;
48           console.log(response.data);
49         }
50       });
51     },
52     components: {
53       'v-header': header
54     }
55   };
56 </script>
57 
58 <style lang="stylus" rel="stylesheet/stylus">
59   @import "common/stylus/mixin.styl"
60   #app
61     .tab
62       display flex
63       width 100%
64       height 40px
65       line-height 40px
66       border-1px(rgba(7, 17, 27, 0.1))
67       .tab-item
68         flex 1
69         text-align center
70         /* &:表示父元素,即.tab-item */
71         & > a
72           /* 區塊點擊纔能有反應 */
73           display block
74           font-size 14px
75           color rgb(77, 85, 93)
76           &.active
77             color rgb(240, 20, 20)
78 </style>
App.vue
  1 <template>
  2   <div class="header">
  3     <div class="content-wrapper">
  4       <div class="avatar">
  5         <img :src="seller.avatar" alt="" width="64" height="64">
  6       </div>
  7       <div class="content">
  8         <div class="title">
  9           <span class="brand"></span>
 10           <span class="name">{{seller.name}}</span>
 11         </div>
 12         <div class="description">
 13           {{seller.description}}/{{seller.deliveryTime}}分鐘送達
 14         </div>
 15         <div class="support" v-if="seller.supports">
 16           <span class="icon" :class="classMap[seller.supports[0].type]"></span>
 17           <span class="text">{{seller.supports[0].description}}</span>
 18         </div>
 19       </div>
 20       <div v-if="seller.supports" class="support-count" v-on:click="showDetail">
 21         <span class="count">{{seller.supports.length}}個</span>
 22         <i class="icon-keyboard_arrow_right"></i>
 23       </div>
 24     </div>
 25     <div class="bulletin-wrapper" v-on:click="showDetail">
 26       <span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
 27       <i class="icon-keyboard_arrow_right"></i>
 28     </div>
 29     <div class="background">
 30       <img :src="seller.avatar" width="100%" height="100%">
 31     </div>
 32     <transition name="fade">
 33       <div v-show="detailShow" class="detail">
 34         <div class="detail-wrapper clearfix">
 35           <div class="detail-main">
 36             <h1 class="name">{{seller.name}}</h1>
 37             <div class="star-wrapper">
 38               <star :size="48" :score="seller.score"></star>
 39             </div>
 40             <div class="title">
 41               <div class="line"></div>
 42               <div class="text">優惠信息</div>
 43               <div class="line"></div>
 44             </div>
 45             <ul v-if="seller.supports" class="supports">
 46               <li class="support-item" v-for="(item, index) in seller.supports">
 47                 <span class="icon" :class="classMap[seller.supports[index].type]"></span>
 48                 <span class="text">{{seller.supports[index].description}}</span>
 49               </li>
 50             </ul>
 51             <div class="title">
 52               <div class="line"></div>
 53               <div class="text">商家公告</div>
 54               <div class="line"></div>
 55             </div>
 56             <div class="bulletin">
 57               <p class="content">{{seller.bulletin}}</p>
 58             </div>
 59           </div>
 60         </div>
 61         <div class="detail-close" v-on:click="hideDetail">
 62           <i class="icon-close"></i>
 63         </div>
 64       </div>
 65     </transition>
 66   </div>
 67 
 68 </template>
 69 
 70 <script type="text/ecmascript-6">
 71   import star from '../star/star.vue';
 72 
 73   export default {
 74     props: {
 75       seller: {
 76         type: Object
 77       }
 78     },
 79 //    vue的data必須用function,由於可能有多個組件實例,組件又能夠複用,爲了相互之間不要互相影響,必須設置data爲function
 80 //    以前有過關於getter和setter知識點的講解
 81 //    vue在new一個是實例是,解析到data函數,會給其中的參數加上getter和setter方法去監聽它的變化
 82     data() {
 83       return {
 84         detailShow: false
 85       };
 86     },
 87     methods: {
 88       showDetail() {
 89         this.detailShow = true;
 90       },
 91       hideDetail() {
 92         this.detailShow = false;
 93       }
 94     },
 95     created() {
 96       this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
 97     },
 98     components: {
 99       star
100     }
101   };
102 </script>
103 
104 <style lang="stylus" rel="stylesheet/stylus">
105   @import '../../common/stylus/mixin'
106 
107   .header
108     position relative
109     /*overflow hidden*/
110     color #fff
111     background rgba(7, 17, 27, 0.5)
112     .content-wrapper
113       position relative
114       padding 24px 12px 18px 24px
115       /* 消除空白字符的影響 */
116       font-size 0 /*background-color rgba(7, 17, 27, 0.5)
117       blur 5px
118       */
119       .avatar
120         /* 左右兩個區域並排顯示 */
121         display inline-block
122         vertical-align top
123         img
124           border-radius 2px
125       .content
126         display inline-block
127         margin-left 16px
128         .title
129           margin 2px 0 8px 0
130           .brand
131             /* 由於是 span ,必須設置爲塊級元素才能指定寬高 */
132             display inline-block
133             /* 由於兩個inline-block是不同搞得,須要設置對齊方式 */
134             vertical-align top
135             width 30px
136             height 18px
137             bg-image('brand')
138             background-size 30px 18px
139             background-repeat no-repeat
140           .name
141             margin-left 6px
142             font-size 16px
143             line-height 18px
144             font-weight bold
145         .description
146           margin-bottom 10px
147           line-height 12px
148           font-size 12px
149         .support
150           margin 10px 0 2px 0
151           .icon
152             display inline-block
153             vertical-align top
154             width 12px
155             height 12px
156             margin-right 4px
157             background-size 12px 12px
158             background-repeat no-repeat
159             &.decrease
160               bg-image('decrease_1')
161             &.discount
162               bg-image('discount_1')
163             &.guarantee
164               bg-image('guarantee_1')
165             &.invoice
166               bg-image('invoice_1')
167             &.special
168               bg-image('special_1')
169           .text
170             line-height 12px
171             font-size 10px
172       .support-count
173         position absolute
174         right 12px
175         bottom 14px
176         padding 0 8px
177         height 24px
178         line-height 24px
179         border-radius 14px
180         background rgba(0, 0, 0, 0.2)
181         text-align center
182         .count
183           /*字體和圖標字體是不同的大小,因此設置vertical-align top進行頂部對齊*/
184           vertical-align top
185           font-size 10px
186         .icon-keyboard_arrow_right
187           margin-left 2px
188           /*由於在base.styl中設置了line-height:1,因此這裏要設置line-height,不然沒法垂直居中*/
189           line-height 24px
190           font-size 10px
191     .bulletin-wrapper
192       position relative
193       height 28px
194       line-height 28px
195       /*要爲文字限定顯示範圍,位置應該限定到箭頭前面*/
196       padding 0 22px 0 12px
197       /*文字超過寬度省略的設置*/
198       white-space nowrap
199       overflow hidden
200       text-overflow ellipsis
201       background rgba(7, 17, 27, 0.2)
202       .bulletin-title
203         display inline-block
204         vertical-align top
205         margin-top 8px
206         width 22px
207         height 12px
208         bg-image('bulletin')
209         background-size 22px 12px
210         background-repeat no-repeat
211       .bulletin-text
212         vertical-align top
213         margin 0 4px
214         text-align center
215         font-size 10px
216       .icon-keyboard_arrow_right
217         position absolute
218         /*由於在base.styl中設置了line-height:1,因此這裏要設置line-height,不然沒法垂直居中*/
219         font-size 10px
220         right 12px
221         bottom 8px
222     .background
223       position absolute
224       top 0
225       left 0
226       width 100%
227       height 100%
228       z-index -1
229       /*使用filter濾鏡產生模糊效果*/
230       filter blur(10px)
231     .detail
232       position fixed
233       z-index 100
234       top: 0
235       left 0
236       width 100%
237       height 100%
238       /*當內容超過屏幕高度,能夠產生滾動條*/
239       overflow auto
240       background-color: rgba(7, 17, 27, 0.8)
241       backdrop-filter: blur(10px)
242       /*從enter到transition,最終顯示效果爲transition*/
243       &.fade-enter-active, &.fade-leave-active
244         transition opacity 0.5s
245       &.fade-enter, &.fade-leave-to
246         opacity 0
247       .detail-wrapper
248         /*要定義寬度,撐開空間,下面一些居中設置才能實現*/
249         width 100%
250         /*鋪滿整個屏幕*/
251         min-height 100%
252         .detail-main
253           margin-top 64px
254           /*下邊距包括兩部分:一部分是邊距,一部風是關閉按鈕的高度*/
255           padding-bottom 64px
256           .name
257             line-height 16px
258             text-align center
259             font-size 16px
260             font-weight 700
261           .star-wrapper
262             margin-top 18px
263             padding 2px 0
264             text-align center
265           .title
266             display flex
267             width 80%
268             margin 28px auto 24px auto
269             .line
270               flex 1
271               position relative
272               top -6px
273               border-bottom 1px solid rgba(255, 255, 255, 0.2)
274             .text
275               padding 0 12px
276               font-weight 700
277               font-size 14px
278           .supports
279             width 80%
280             margin 0 auto
281             .support-item
282               padding 0 12px
283               margin-bottom 12px
284               font-size 0
285               &:last-child
286                 margin-bottom 0
287               .icon
288                 display inline-block
289                 vertical-align top
290                 width 16px
291                 height 16px
292                 margin-right 6px
293                 background-size 16px 16px
294                 background-repeat no-repeat
295                 &.decrease
296                   bg-image('decrease_2')
297                 &.discount
298                   bg-image('discount_2')
299                 &.guarantee
300                   bg-image('guarantee_2')
301                 &.invoice
302                   bg-image('invoice_2')
303                 &.special
304                   bg-image('special_2')
305               .text
306                 /*與圖片高度同樣,才能垂直居中*/
307                 line-height 16px
308                 font-size 12px
309           .bulletin
310             width 80%
311             margin 0 auto
312             .content
313               padding 0 12px
314               line-height 24px
315               font-size 12px
316       .detail-close
317         position relative
318         width 32px
319         height 32px
320         margin -64px auto 0 auto
321         clear both
322         font-size 32px
323 </style>
header.vue
 1 <template>
 2   <div class="star" :class="starType">
 3     <span v-for="itemClass in itemClasses" :class="itemClass" class="star-item"></span>
 4   </div>
 5 </template>
 6 
 7 <script type="text/ecmascript-6">
 8   const LENGTH = 5;
 9   const CLS_ON = 'on';
10   const CLS_HALF = 'half';
11   const CLS_OFF = 'off';
12 
13   export default {
14     props: {
15       size: {
16         type: Number
17       },
18       score: {         // 評價的分數
19         type: Number
20       }
21     },
22     computed: {
23       starType() {
24         return 'star-' + this.size;
25       },
26       itemClasses() {
27         let result = []; // 最終全部星星存放的數組
28         let score = Math.floor(this.score * 2) / 2;
29         let hasDecimal = score % 1 !== 0;   // 小數
30         let integer = Math.floor(score);    // 整數
31         for (let i = 0; i < integer; i++) { // 全星
32           result.push(CLS_ON);
33         }
34         if (hasDecimal) { // 是否有半星(半星最多有一個)
35           result.push(CLS_HALF);
36         }
37         while (result.length < LENGTH) {    // 無星
38           result.push(CLS_OFF);
39         }
40         return result;
41       }
42     }
43   };
44 </script>
45 
46 <style lang="stylus" rel="stylesheet/stylus">
47   @import '../../common/stylus/mixin.styl';
48 
49   .star
50     /*清除空白字符影響*/
51     font-size 0
52     .star-item
53       /*星星是橫向排列*/
54       display inline-block
55       background-repeat no-repeat
56     &.star-48
57       .star-item
58         width: 20px
59         height: 20px
60         margin-right: 22px
61         background-size: 20px 20px
62         &:last-child
63           margin-right: 0
64         &.on
65           bg-image('star48_on')
66         &.half
67           bg-image('star48_half')
68         &.off
69           bg-image('star48_off')
70     &.star-36
71       .star-item
72         width: 15px
73         height: 15px
74         margin-right: 6px
75         background-size: 15px 15px
76         &:last-child
77           margin-right: 0
78         &.on
79           bg-image('star36_on')
80         &.half
81           bg-image('star36_half')
82         &.off
83           bg-image('star36_off')
84     &.star-24
85       .star-item
86         width: 10px
87         height: 10px
88         margin-right: 3px
89         background-size: 10px 10px
90         &:last-child
91           margin-right: 0
92         &.on
93           bg-image('star24_on')
94         &.half
95           bg-image('star24_half')
96         &.off
97           bg-image('star24_off')
98 </style>
star.vue
 1 body, html
 2   margin 0
 3   padding 0
 4   line-height 1
 5   font-weight 200
 6   // 字體查找順序,從前到後,若是全部設置字體都不認識,則返回系統字體
 7   font-family 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', Arial, sans-serif
 8 
 9 ul
10   list-style none
11   padding-left 0
12   margin 0
13 
14 li
15   list-style none
16 
17 h1
18   margin 0
19 
20 .clearfix
21   display inline-block
22   &:after
23     display block
24     content "."
25     height 0
26     line-height 0
27     visibility hidden
28     clear both
29 
30 //爲何要寫 -webkit :由於這裏沒有兼容性文件控制,因此要手寫兼容性代碼
31 //本例須要的是1像素的邊框,因此當dpr爲1.5時,取0.7倍(1.5*0.7=1.05約等於1)。dpr爲2時,取0.5倍(2*0.5=1)
32 @madia (-webkit-min-device-pixel-ratio 1.5)
33 (-min-device-pixel-ratio 1.5)
34 .border-1px
35   &::after
36     -webkit-transform scaleY(0.7)
37     transform scaleY(0.7)
38 
39 @madia (-webkit-min-device-pixel-ratio 2)
40 (-min-device-pixel-ratio 2)
41 .border-1px
42   &::after
43     -webkit-transform scaleY(0.5)
44     transform scaleY(0.5)
base.styl
 1 border-1px($color)
 2   position relative
 3   // 進行僞類設置,必定要設置寬度,由於元素脫離了文檔流
 4   &:after
 5     display block
 6     position absolute
 7     left 0
 8     bottom 0
 9     width 100%
10     border-top 1px solid $color
11     content ' '
12 
13 border-none()
14   &:after
15     display none
16 
17 bg-image($url)
18   // 正常狀況下
19   background-image url($url + "@2x.png")
20   // 最小 dpr=3 的狀況下
21   @madia (-webkit-min-device-pixel-ratio 3) (-min-device-pixel-ratio 3)
22     background-image url($url + "@3x.png")
mixin.styl
相關文章
相關標籤/搜索