1. 程式人生 > >vue2.x餓了嗎實戰總結

vue2.x餓了嗎實戰總結

仿餓了麼app是基於vue2.x最新實戰專案,用到的技術棧

vue2 + vue-router2 + vue-cli2+ axios + stylus + webpack2 + node.js

頁面相對簡單,所以沒有用到vuex, 它更適合對複雜的單頁面進行狀態管理

實現功能:

•    Goods、Ratings、Seller元件檢視均可上下滾動
•    商品頁 點選左側menu,右側list對應跳轉到相應位置
•    點選list檢視商品詳情頁,父子元件的通訊
•    評論內容夠可以篩選檢視
•    購物車元件,包括新增刪除商品及動效,購物控制元件與購物車元件之間非父子元件通訊,點選購物車圖示,展示選擇的商品列表
•    商家實景圖片可以左右滑動 
•    loaclStorage快取商家資訊(id
name

如果覺得對您有幫助,您可以在右上角給我個star支援一下,謝謝!

1-專案結構分析:

cmmon/---- 資料夾存放的是通用的css和fonts
components/----資料夾用來存放我們的 Vue 元件
router/----資料夾存放的是vue-router相關配置(linkActiveClass,routes註冊元件路由)
build/----檔案是 webpack 的打包編譯配置檔案
config/----資料夾存放的是一些配置項,比如我們伺服器訪問的埠配置等
dist/----該資料夾一開始是不存在,在我們的專案經過 build 之後才會產出
prod.server.js----該檔案是測試是模擬的伺服器配置,用來執行dist裡面的檔案,在config/index.js中,build物件中新增一條埠設定port:9000,
App.vue----根元件,所有的子元件都將在這裡被引用,eventHub空例項是用來元件間通訊的中央資料匯流排作用,主要連線購買控制元件和購物車元件之間的資料通訊 index.html----整個專案的入口檔案,將會引用我們的根元件 App.vue main.js----入口檔案的 js 邏輯,在 webpack 打包之後將被注入到 index.html 中

2-各元件之間的關係:

├──src
    ├──Header.vue--頭部元件
      ├──iconClassMap--圖示元件(減,折,特,票,保)
      ├──Star.vue--星星評分元件
    ├──Goods
.vue--商品元件 ├──iconClassMap--圖示元件(減,折,特,票,保) ├──Shopcart.vue--購物車元件,包括小球飛入購物車動畫,使用this.\$root.eventHub.\$on('cart.add', this.drop)接收,並給drop方法使用 ├──CartControl.vue--購買控制元件--選中數量返回給父元件goods,goods響應後,重新計算選中數量,並用this.\$root.eventHub.\$emit('name',event.target)將資料傳送給購物車元件, ├──Foodinfo.vue--商品詳情頁 ├──RatingSelect.vue--評價內容篩選元件 ├──Ratings.vue--評論元件 ├──RatingSelect.vue--評價內容篩選元件 ├──Seller.vue--商家元件 ├──iconClassMap--圖示元件(減,折,特,票,保) 獨立元件 ├──iconClassMap--圖示元件(減,折,特,票,保) ├──split.vue--關於分割線元件 ├──RatingSelect.vue--評價內容篩選元件

3-開發過程問題彙總:

3-1、better-scroll外掛在移動端使用時需要設定click:true,否則移動端滑動無效

3-2、分開設定css樣式:

  • 圖示icon.css--文字圖示樣式,通過icommon.io網站 將svg圖片轉成文字圖示樣式

  • 公共base.css--處理裝置畫素比的一些樣式,針對border-1px問題,不同裝置畫素比,顯示的線條粗細不同

  • 工具mixin.css--設定border-1px樣式和背景樣式

3-2-1、這裡著重解釋一下border-1px的實現

當樣式畫素一定時,因手機有320px,640px等.各自的縮放比差異,所以裝置顯示畫素就會有1Npx,2Npx.為保設計稿還原度,解決就是用media + scale.

  • 公式:裝置上畫素 = 樣式畫素 * 裝置畫素比

螢幕寬度: 320px 480px 640px
裝置畫素比: 1    1.5    2

通過查詢它的裝置畫素比 devicePixelRatio

在裝置畫素比為1.5倍時, round(1px 1.5 / 0.7) = 1px 
在裝置畫素比為2倍時, round(1px 2 / 0.5) = 1px
// stylus語法
border-1px($color)
    position:relative
    &:after
        content:''
        display:block
        position:absolute
        left:0
        bottom:0
        width:100%
        border:1px solid $color
        
@media(-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5)
    .border-1px
        &::after
            -webkit-transform:scaleY(0.7)
            transform:scaleY(0.7)
            
@media(-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2)
    .border-1px
        &::after
            -webkit-transform:scaleY(0.5)
            transform:scaleY(0.5)

3-3、sticky-footer佈局

主要特點是如果內容不夠長,頁尾部分也會貼在視窗底部,內容足夠長,就會將頁尾推到內容底部,父級position:fixed,min-height:100%;內容設為padding-bottom:64px,頂部,margin-top:-64px

3-4、要求自適應的佈局

3-4-1、左側寬度固定,右側寬度自適應

// 左側固定width:80px,右側自適應
parent:
    display:fiexd;
child-left:
    flex:0 0 80px
child-right:
    flex:1

3-4-2、元素寬度自適應裝置寬度,且元素要求等寬高樣式

例如:商品詳情頁面的商品圖片展示樣式

// stylus語法
.img_header
    position:relative
    width:100% // width是 裝置寬度
    height:0
    padding-top:100% // 高度設為0,使用padding撐開
    .img
        position:absolute //定位佈局
        top:0
        left:0
        width:100%
        height:100%

3-5、背景模糊效果

filter:blur(10px),注意,所有在內的子元素也會模糊,包括文字,所以採用定位佈局,背景單獨佔用一個層,ios有一個設定backdrop-filter:blur(10px),只會模糊背景,但不支援android

3-6、transition過渡

在購買控制元件中使用transition過渡效果,實現新增減少按鈕的動效,和小球飛入購物車的動效(模仿貝塞爾曲線的效果)

vue2.x裡面定義了transition過渡狀態,
name - string, 用於自動生成 CSS 過渡類名。

例如:name: 'fade' 將自動拓展為.fade-enter,.fade-enter-active等。預設類名為 "v"

fade-enter
fade-enter-active
fade-leave
fade-leave-active
            

包括transition過渡的鉤子函式

before-enter
before-leave
before-appear
enter
leave
appear
after-enter
after-leave
after-appear
enter-cancelled
leave-cancelled (v-show only)
appear-cancelled

3-7、seller元件:

3-7-1問題一:seller頁面中商品商家實景圖片橫向滾動

解決方案:每個li要display:inline-block,因為width不會自動撐開父級ul,所以需要計算ul的width,(每一張圖片的width+margin)*圖片數量-一個margin,因為最後一張圖片沒有margin
同時new BScroll裡面要設定scrollX: true,eventPassthrough: 'vertical',// 滾動方向橫向

3-7-2問題二:開啟seller頁面,無法滾動

問題分析:出現這種現象是因為better-scroll外掛是嚴格基於DOM的,資料是採用非同步傳輸的,頁面剛開啟,DOM並沒有被渲染,所以,要確保DOM渲染了,才能使用better-scroll,
解決方案:用到mounted鉤子函式,同時搭配this.$nextTick()

3-7-3問題三:在seller頁面,重新整理後,無法滾動

問題分析:出現這種情況是因為mounted函式在整個生命週期中只會只行一次
解決方案:使用watch方法監控資料變化,並執行滾動函式 this._initScroll();this._initPicScroll();

3-8、快取資料

使用window.localStorage儲存和設定快取資訊,封裝在store.js檔案內

//將頁面資訊儲存到localStorage裡
export function saveToLocal(id, key, value) {
  let store = window.localStorage._store_; // 新定義一個key值_store_,存放要儲存的資料物件
  // _store_ {
  //   store[id]: {
  //     key: value
  //   }
  // }
  if (!store) {
    store = {};
    store[id] = {};
  } else {
    store = JSON.parse(store); // String格式--> json格式
    if (!store[id]) {
      store[id] = {};
    }
  }
  store[id][key] = value;
  window.localStorage._store_ = JSON.stringify(store); // 將json格式轉成String格式,存放到window.localStorage._store中
}
//將localStorage資訊設定到頁面中
export function loadFromLocal(id, key, defaults) {
  let store = window.localStorage._store_;
  if (!store) { // 一開始是沒有的,因為沒有點選事件,所以顯示預設資料
    return defaults;
  }
  store = JSON.parse(store)[id]; // 將json格式-->String格式
  // console.log(store); // {"isFavorite":true}
  if (!store) {
    return defaults;
  }
  let ret = store[key];
  return ret || defaults;
}

3-9、解析url,得到商家資訊,包括id,name,在獲取資料時,直接賦值,商家的id或name會被丟掉

使用window.localStorage.search獲取url地址,並進行解析
封裝在tools.js檔案內

/**
 * http://localhost:8080/#/Seller
 *  https://h5.ele.me/shop/#id=151667422
 * ?id=1234&name=zpxf
 */
 
/////////方法一:
export function urlParse() {
  let url = window.location.search;
  let obj = {};
  let reg = /[?&][^?&]+=[^?&]+/g;
  let arr = url.match(reg);
  // ['?id=12345', '&a=b']
  if (arr) {
    arr.forEach((item) => {
      let tempArr = item.substring(1).split('=');
      // 因為tempArr是url中的引數,所以要用decode進行轉化
      let key = decodeURIComponent(tempArr[0]);
      let val = decodeURIComponent(tempArr[1]);
      obj[key] = val;
    });
  }
  return obj;
};

/////////方法二:
export function urlParse() {
  let urlArr = window.location.search.substr(1).split('&'); // 擷取掉?,並以&分開,存入陣列
  // console.log(urlArr); // ["id=1234", "name=zpxf"]
  let obj = {};
  if (urlArr) {
    urlArr.forEach((item) => {
      let arr = item.split('='); // 每一項用=分開存入陣列,arr[0]=key,arr[1]=value
      // console.log(arr); // [id,1234] [name,zpxf]
      let key = decodeURIComponent(arr[0]); // 對url解碼
      let val = decodeURIComponent(arr[1]);
      obj[key] = val;
    });
  }
  // console.log(obj); // {id: "1234", name: "zpxf"}
  return obj;
};

我們需要將得到的id和name帶到資料中,實際上在獲取資料的時候,並沒有帶著id和name,這時就要用到es6語法中Object.assign(),官方解釋為:可以把任意多個的源物件自身的可列舉屬性拷貝給目標物件,然後返回目標物件。

this.seller = Object.assign({}, this.seller, response.data);

//即將vm.seller屬性和請求返回資料物件合併到空物件,然後賦值給vm.seller,這裡加上this.seller即提供了一種可擴充套件的機制,倘若原來的屬性中有預定義的其他屬性。

3-10、goods,ratings,seller元件之間切換時會重新渲染

解決方案:在app.vue內使用keep-alive,保留各元件狀態,避免重新渲染

<keep-alive>
    <router-view :seller="seller"></router-view>
</keep-alive>

4-專案總結

4-1、vue-router

使用<router-link>元件完成導航,<router-link>預設會被渲染成一個 <a> 標籤,但必須使用to屬性,指定連線

 <!-- 導航 -->
<router-link to="/home">home</router-link>
<router-link to="/about">about</router-link>

<!-- 路由出口 元件渲染容器 -->
<router-view></router-view>
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Resource);
// 定義每個路由對應一個元件
let router = new Router({ // 建立 router 例項,然後傳 `routes` 配置
  linkActiveClass: 'active',
  routes: [{
      path: '/Header',
      name: 'Header',
      component: Header
    },
    {
      path: '/Seller',
      name: 'Seller',
      component: Seller
    },
    {
      path: '/Goods',
      name: 'Goods',
      component: Goods
    },
    {
      path: '/Ratings',
      name: 'Ratings',
      component: Ratings
    }
  ]
});
export default router;
router.push('goods');// 相當於頁面初始化,顯示goods的內容

// 掛載
new Vue({
  el: '#app',
  template: '<App/>',
  router: router,
  components: { App }
});

//或者另一種掛載
new Vue({
  template: '<App/>',
  router: router,
  components: { App }
}).$mount(#app);//手動掛載,#app

4-2、vue-resource






4-3、Object.assign(target, source1, source2);

這是es6的語法,用於物件合併,將源物件(source)的所有可列舉屬性,複製到目標物件(target)。

Object.assign方法的第一個引數是目標物件,後面的引數都是源物件。

注意,如果目標物件與源物件有同名屬性,或多個源物件有同名屬性,則後面的屬性會覆蓋前面的屬性。

var target = { a: 1, b: 1 };

var source1 = { b: 2, c: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

另外需要注意的是Object.assign()方法只會拷貝源物件自身的並且可列舉的屬性到目標身上。也就意味著繼承屬性和不可列舉屬性是不能拷貝的,而且拷貝是物件的屬性的引用而不是物件本身。

4-4、元件間通訊

vue是元件式開發,所以元件間通訊是必不可少的。vue提供了一種方式,即在子元件定義props來傳遞父元件的資料物件。

// 父元件
<v-header :seller="seller"></v-header>

// 子元件 header.vue
props: {
  seller: {
    type: Object
  }
}

如果是子元件想傳遞資料給父元件,需要派發自定義事件,使用$emit派發,
父元件使用v-on接收監控(v-on可以簡寫成@)


// 子元件 RatingSelect.vue,派發自定義事件isContent,將this.onlyContent資料傳給父級

this.$emit('isContent', this.onlyContent);
this.$emit('selRatings', this.selectType);

// 父元件 foodInfo.vue 在子元件的模板標籤裡,使用v-on監控isContent傳過來的資料

<v-ratingselect :ratings="food.ratings" :select-type="selectType" :only-content="onlyContent" :desc="desc" @selRatings="filterRatings" @isContent="iscontent"></v-ratingselect>

非父子元件之間通訊,vue官方銳減使用vueX,但是這裡相較簡單,所以採用的是利用給一個空例項eventHub,作為兩個元件的中央資料匯流排,使用this.$root.eventHub.$emit來派發自定義事件,使用this.$root.eventHub.$on來監控
這裡特別說明$root,官方解釋:表示當前組建樹的根例項,如果根例項沒有父例項,次例項將會是自己

//main.js
new Vue({
  // el: '#app',
  router,
  template: '<App/>',
  components: {
    App
  },
  data: {
    eventHub: new Vue() // 給data新增一個 名字為eventHub 的空vue例項,用來傳輸非父子元件的資料
  }
}).$mount('#app'); // 手動掛載,#app



//foodInfo.vue元件派發自定義事件cart.add,傳遞資訊event.target
this.$root.eventHub.$emit('cart.add', event.target); // 傳輸點選的目標元素


//Shopcart.vue元件監控cart.add
created() {
    // 獲取按鈕元件的點選的元素,用在drop方法裡
    this.$root.eventHub.$on('cart.add', this.drop);
},
methods:{
    drop(element){
        //to do ...
    }
}

3、父元件向子元件傳遞引數在子元件中通過props接收在父元件中通過:seller 通過api獲取並且在data中定義

<router-view:seller="seller">content</router-view>子元件:

props:{ //這裡router 路由的時候將這個傳過來了所以可以直接使用 seller:{ type:Object } }

4、子元件向父元件傳遞引數 通過$emit一般是定義一個show或者是click事件然後通過$emit傳遞

eg:子級:

togglecontent(event){
  if(!event._constructed){
    return;//設定他的原生事件不觸發
}
  this.onlycontent = !this.onlycontent;
  this.$emit("toggleContent",this.onlycontent);
}
//這裡toggleContent表示在父級中呼叫的引數的呼叫名

父級:

a、先匯入元件

import rating from '../rating/rating.vue';

b、然後註冊

components:{
  rating
}

c、在頁面引用這個元件

<rating :selectedType="selectedType"  v-on:ratingtypeSelect="ratingtypeselect" v-on:toggleContent="togglecontent"  :onlyContent="onlyContent" :destype="destype" :rating="food.ratings"></rating>

通過@toggleContent='togglecontent'或者v-on:toggleContent='togglecontent'

d、通過togglecontent(){}這個方法將子級定義出來的togglecontent值賦值給父級

togglecontent(onlyContent){
  this.onlyContent = onlyContent;
  this.$nextTick(() =>{
    this.scroll.refresh();
  })
},

4-5、元件提取管理

將相同樣式或功能的區塊單獨提出來,作為一個元件。

另外元件中用到的圖片等資源就近維護,即可以考慮在元件資料夾中新建images資料夾。

抽離元件遵循原則:
要儘量遵循單一職責原則,複用性更高,不要設定額外的margin等影響佈局的東西

5-css前處理器--stylus

全域性安裝,安裝之前你需要你安裝 nodejs

$ npm install stylus -g

index.styl是stylus檔案的入口檔案,裡面使用@import 引入各種styl檔案

@import './mixin.styl'
@import './base.styl'
@import './icon.styl'

在入口檔案main.js中全域性引用index.styl

import 'common/stylus/index.styl';
// 使用stylus可以快速且保證相容的實現border-1px:
//mixin.styl

border-1px($color)
    position:relative
    &:after
        content:''
        display:block
        position:absolute
        left:0
        bottom:0
        width:100%
        border:1px solid $color
@media(-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5)
    .border-1px
        &::after
            -webkit-transform:scaleY(0.7)
            transform:scaleY(0.7)
@media(-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2)
    .border-1px
        &::after
            -webkit-transform:scaleY(0.5)
            transform:scaleY(0.5)        
        

6-開啟app應用,預設顯示goods內容

想要達到這種目的,有兩種方法,一種是利用重定向,另一種是利用vue-router的導航式程式設計。

6-1、重定向

//在router的index.js檔案中設定,要多寫一個物件,指向目標元件
routes: [
    {
      path: '/',
      redirect: '/Goods',// 重定向
      name: 'Goods',
      component: Goods
    },
    {
      path: '/Goods',
      name: 'Goods',
      component: Goods
    },
    {
      path: '/Header',
      name: 'Header',
      component: Header
    },
    {
      path: '/Seller',
      name: 'Seller',
      component: Seller
    },
    {
      path: '/Ratings',
      name: 'Ratings',
      component: Ratings
    }
  ]

6-2、導航式程式設計

router.push('/Goods');

7-關於eslint

eslint 是一個js程式碼風格檢查器,配合vue-cli腳手架中的熱更新,可以很方便的定位和提示錯誤。在公司多人協作開發時可以確保程式碼風格保持一致,可以很方便的閱讀他人的程式碼。

剛使用時,會不太習慣,但是堅持下來,自己寫的程式碼越來越整齊規範,越來越漂亮,自己會有很大的滿足感。對自己,對他人都是一件非常有益的事!

8-關於其他

8-1、vue2相較vue1有很多地方改動

比如

  • v-for的書寫格式,多出:key值,而且必須寫

  • transition書寫格式不在是在元素標籤上寫,而是作為一個標籤<transition></transition>將目標元素包起來,過渡狀態變為4種狀態

  • v-el ,v-ref 最後都轉化為了 ref ref="xxx"eg:<divclass='parent'ref='showhello'>hello</div> js中 this.$refs.showhello.innerText()

    a、ref在官網上的解釋簡單來說就是用來繫結某個dom元素,或者來說用來繫結某個元件,然後在$refs裡面呼叫,

    b、如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素; 如果用在子元件上,引用就指向元件例項

    c、$refs作為子元件索引,ref本身是作為渲染結果被建立的,在初始渲染的時候你不能訪問它們 - 它們還不存在!$refs 也不是響應式的,因此你不應該試圖用它在模版中做資料繫結

8-2、專案執行

克隆專案到本地
git clone https://github.com/JerryYgh/m-eleme.git

安裝依賴
npm install

本地開發,開啟伺服器,瀏覽器訪問http://localhost:8080
npm run dev

構建生產
npm run build

執行打包檔案
node prod.server.js 

會看到 Listening at http://localhost:9000 在瀏覽器中開啟即可

8-2瀏覽器相容問題解決

postcss外掛用來解決瀏覽器的相容問題

8-4



9-學習參考