最新 vue2.x 仿餓了麼app商家頁面 項目總結

最新vue2.x仿餓了麼app 商家頁面 項目總結

標籤(空格分隔): vuejscss


前言

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

vue2 + vue-router2 + vue-cli2 + vue-resource + stylus + flex佈局 + es6 + eslint + webpack2

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

實現功能:

•    Goods、Ratings、Seller組件視圖都可上下滾動
•    商品頁 點擊左側menu,右側list對應跳轉到相應位置
•    點擊list查看商品詳情頁,父子組件的通訊
•    評論內容夠能夠篩選查看
•    購物車組件,包括添加刪除商品及動效,購物控件與購物車組件之間非父子組件通訊,點擊購物車圖標,展現選擇的商品列表
•    商家實景圖片能夠左右滑動 
•    loaclStorage緩存商家信息(id、name)

項目地址:https://github.com/moxiaojing...node

若是以爲對您有幫助,您能夠在右上角給我個star支持一下,謝謝!android

1-項目結構分析:

common/---- 文件夾存放的是通用的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-各組件之間的關係:

├──APP.vue
  │  ├──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-一、better-scroll插件在移動端使用時須要設置click:true,不然移動端滑動無效

3-二、分開設置css樣式:

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

  • 公共base.css--處理設備像素比的一些樣式,針對border-1px問題,不一樣設備像素比,顯示的線條粗細不一樣ios

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

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

當樣式像素必定時,因手機有320px,640px等.各自的縮放比差別,因此設備顯示像素就會有1Npx,2Npx.爲保設計稿還原度,解決就是用media + scale.git

  • 公式:設備上像素 = 樣式像素 * 設備像素比es6

屏幕寬度: 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)

更多瞭解設備像素比devicePixelRatiohttp://www.zhangxinxu.com/wor...

3-三、sticky-footer佈局

header組件的詳情頁採用sticky-footer佈局,主要特色是若是內容不夠長,頁腳部分也會貼在視窗底部,內容足夠長,就會將頁腳推到內容底部,父級position:fixed,內容設爲padding-bottom:64px,頁腳相對定位,margin-top:-64px

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

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

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

3-4-二、元素寬度自適應設備寬度,且元素要求等寬高樣式

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

// 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-五、背景模糊效果

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

3-六、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

詳情請查看vue2.x-transition詳解

3-七、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-八、緩存數據

使用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-九、解析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-十、goods,ratings,seller組件之間切換時會從新渲染

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

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

4-項目總結

4-一、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-二、vue-resource

經過 this.$http.get來定義經過vue實例來發送get請求,而後經過then後面的回調函數將請求成功的數據接收,經過狀態碼來判斷是否成功以及複製給vue的數據對象。因爲這裏是用的mock數據(模擬後臺數據),因此用的模擬狀態碼。

同時,這裏省略了errorcallback的定義,正常開發中須要進行定義,甚至能夠利用vue-resource的inteceptor進行體驗優化,好比定義請求時的loading動畫界面。在vue中便可以提取出loading組件。

const ERR_OK = 0;//表示沒有錯誤信息,即獲取數據成功
this.$http.get('/api/seller').then((response) => {
  response = response.body;
  if (response.errno === ERR_OK) {
    this.seller = Object.assign({}, this.seller, response.data);
  }
});

4-三、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-四、組件間通信

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 ...
    }
}

4-五、組件提取管理

將相一樣式或功能的區塊單獨提出來,做爲一個組件。

另外組件中用到的圖片等資源就近維護,便可以考慮在組件文件夾中新建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-一、重定向

//在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-二、導航式編程

router.push('/Goods');

7-關於eslint

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

剛使用時,會不太習慣,可是堅持下來,本身寫的代碼愈來愈整齊規範,愈來愈漂亮,本身會有很大的知足感。對本身,對他人都是一件很是有益的事!

8-關於其餘

8-一、vue2相較vue1有不少地方改動

好比

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

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

  • v-el 和 v-ref 都棄用,改成使用ref屬性爲元素或組件添加標記,經過$refs獲取

具體詳細內容,請參看官網從vue1.x遷移

8-二、項目運行

克隆項目到本地
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-三、手機測試網頁技巧

將localhost換成本身的ip,Windows在命令行執行ipconfig查看,mac執行ifconfig查看。

而後複製地址欄地址,進入草料二維碼,而後生成二維碼,而後用手機掃一掃就能夠查看了,前提是,你手機和電腦必須在同一個局域網。

9-學習參考

相關文章
相關標籤/搜索