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

前言:學習Vue.js高仿餓了麼課程過程中,總結了這個Web App項目從準備到開發完畢自己覺得很重要的知識點。這一篇主要介紹:項目準備、頁面骨架開發、header組件開發。



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

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

Appheader

一、項目分析&學習目標

當前最火的MVVM框架

  • Vue.js —— 輕量、簡潔、高效、數據驅動、組件化

高仿上線外賣App標準來開發

  • 核心模塊 —— 商家模塊

開發一個webApp的全流程

  1. 需求分析
  2. 腳手架工具
  3. 數據mock
  4. 架構設計
  5. 代碼編寫
  6. 自測
  7. 編譯打包

以線上生產環境的代碼質量作標準

  • 代碼開發及測試環節:
  1. UI標註
  2. 真實數據演示
  • 代碼規範
  1. 架構設計
  2. 組件抽象
  3. 模塊拆分
  4. 代碼風格統一
  5. JS變量命名規範
  6. CSS代碼規範

功能技術分析

  • vue-resource: 和後端做數據交互
  • vue-router: 做前端路由,實現單頁應用
  • 第三方JS庫better-scroll: 列表滾動的實現
  • 最大程度組件化: 提高代碼的複用
  • html5的localstorage:【收藏商家】功能—存儲在瀏覽器端
  • 細節:圖標字體的使用
  • 移動端1像素邊框
  • css sticky footer佈局
  • flex 彈性佈局

學習目標

  • 掌握Vue.js在實戰中的運用
  • 學會使用Vue.js【完整的】開發移動端App
  • 學會組件化、模塊化的開發

學習內容

  • Vue.js框架介紹
  • Vue-cli 腳手架 —— 搭建基本代碼框架
  • vue-router 官方插件 —— 管理路由
  • vue-resource 官方插件 —— 和後端作Ajax通信
  • Webpack 開源構建工具(把源代碼經過編譯生成瀏覽器可以識別和運行的代碼)
  • es6 + eslint eslint —— es6代碼風格檢查工具
  • 工程化 組件化 模塊化
  • 移動端常用開發技巧:
  1. flex彈性佈局
  2. css stickyfooter
  3. 酷炫的交互設計

二、Vue.js介紹

近年來前端開發趨勢

  • 舊瀏覽器逐漸淘汰,移動端需求增加
  • 前端交互越來越多,功能越來越複雜
  • 架構從傳統後臺MVC 向REST API+ 前端MV* 遷移

(前者傳統MVC:更新數據會刷新頁面 後者前端MV*: 向後端REST API異步請求數據,局部刷新頁面)

MV* —— MVC、MVP、MVVM

MVVM框架

View ViewModel Model

視圖 通訊 數據

  • DOM 觀察者 Javascript對象
  1. 針對具有複雜交互邏輯的前端應用
  2. 提供基礎的架構抽象
  3. 通過Ajax數據持久化,保證前端用戶體驗
  • MVVM框架技術:vue.js、react.js、Angular.js

對比Anglar React

  • Vue.js更輕量,gzip後大小隻有 20K+
  • Vuejs更易上手,學習曲線平穩
  • 吸收兩家之長,借鑑了angular的指令和react的組件化

vue.js 核心思想

  • 數據驅動
  • 組件化

組件設計原則

  • 頁面上每個獨立的可視/可交互區域視為一個組件
  • 每個組件對應一個工程目錄,組件所需要的各種資源在這個目錄下【就近維護】
  • 頁面不過是組件的容器,組件可以嵌套自由組合形成完整的頁面

三、Vue-cli開啟Vue項目

Vue-cli 是Vue的腳手架工具 —— 幫助寫好Vue基礎代碼的工具

  1. 目錄結構
  2. 本地調試
  3. 代碼部署
  4. 熱加載
  5. 單元測試

安裝使用

(sudo) npm install -g vue-cli // sudo:mac環境下有關管理權限的命令

vue init webpack my-project

項目文件

  • src文件夾:存放項目源碼
  • bulld目錄+ config目錄:webpack配置相關
  • node_modules文件夾:npm install 安裝的依賴代碼庫
  1. static—>.gitkeep: 當這個目錄為空時也可以將它提交到git倉庫中
  2. babelrc : babel的一些配置,es6語法的轉換
  3. .editorconfig: 編輯器的配置
  4. .eslintignore: 忽略語法檢查的目錄文件,一般忽略build目錄和node_modules目錄
  5. .eslintrc.js: eslint的配置文件
  6. gitignore: 上傳git倉庫要忽略的一些文件的配置
  7. index.html: 入口html文件,要使用的css和js文件會在編譯過程中自動插入
  8. package.json:整個項目的配置文件,一般用來描述項目 ↓

→>

→ dependencies:開發環境中的依賴

→ devdependencies: 編譯過程中的依賴

項目運行

npm run dev 

  • src開發目錄下:
  1. main.js —— 項目入口文件
  2. App.vue —— 主頁面組件
  • vue語法糖: export default { } 一個對象——可以定義一個組件

【小知識點】sublime自動格式化 —— Command+option+L 或 Control+alt+L

  • 在父組件中使用子組件,如Hello.vue:
  1. 引用
import Hello from './compoments/Hello'
  1. 註冊
export default{
components: {
Hello //es6語法 相當於 'Hello': Hello
}
}
  1. 使用標籤

開發時的Webpack配置與編譯

  • build->dev-server.js 或 Webpack.dev.conf.js
  • webpack.base.conf.js : 配置各種文件的Loader

→ 配置默認識別的路徑


resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
}



四、準備工作

圖標字體制作

  • 在線製作網站:https://icomoon.io/app/#/select
  • 將自己的SVG圖標導入,輸出自己的圖標字體文件
  • Import Icons → Generate Fonts → preferences修改名稱 → Download
  • 使用:icon.css和fonts文件夾下所有文件

項目目錄設計

  • src->common目錄下:項目公用文件 js、style、fonts

【css的stylus語法】

  • 刪掉分號和大括號, &表示父元素,冒號也可以省略
  • 文件後綴為styl,style中添加lang="stylus"
  • 需要安裝:

1

npm install stylus stylus-loader --save-dev


  • resource目錄下:項目圖片文件——可以刪掉無用的assets目錄,但需要修改引用到的地方
  • components目錄下:佈局、業務功能等分模塊管理組件,如header目錄,footer目錄
  • static->css目錄下:reset.css 標籤默認樣式
  • 在 index.html 中引入:

前後端分離

  • Vue SPA —— 前端通過 vue-resource Ajax從後端獲取數據
  • 前端最重要的任務:mock數據(後臺數據模擬) data.json
{
"seller":{} //商家相關字段
"goods":{} //商品相關字段
"rattings":{} //評論相關字段
} 

webpack.dev.conf.js中配置

  • 使用 express框架 開啟一個node server,用 express.Router 編寫這些接口請求
  1. 首先:在 const portfinder = require(‘portfinder’) 後添加

const express = require('express')//開啟一個node server
const app = express() //定義一個對象,包含express返回的數據
var appData = require('../data.json') //定義一個對象引入data數據
var seller = appData.seller;
var goods = appData.goods;
var ratings = appData.ratings;
app.use('/api', apiRoutes); //調用app對象


  1. 然後:找到 devserver{}, 在裡面添加

before(app) {
app.get('/api/seller', (req, res) => {
res.json({
errno: 0, //錯誤碼:實際上是業務方根據業務自己定的
data: seller
}) //接口返回json數據,上面配置的數據seller就賦值給data請求後調用
}),
app.get('/api/goods', (req, res) => {
res.json({
errno: 0,
data: goods
})
}),
app.get('/api/ratings', (req, res) => {
res.json({
errno: 0,
data: ratings
})
})
}


注意:每次配置完 express 之後都需要重新啟動

查看json數據

  • 在Google地址欄中輸入:localhost:8080/api/seller
  • 依賴Google的jsonview插件 —— 安裝 使數據格式化

【Google安裝第三方插件】

  • 打開https://github.com ;
  • 搜索 jsonView 鏈接:https://github.com/search?utf8=%E2%9C%93&q=jsonview;
  • 選擇需要的插件(我下載的是這個gildas-lormeau/JSONView-for-Chrome);
  • 點擊【Download Zip】,插件下載完成,解壓縮到相應目錄(D:\Download\JSONView-for-Chrome-master);
  • 安裝,打開chrome - 擴展程序 (地址欄輸入chrome://extensions/);
  • 右上角,選中開發模式;
  • 點擊”加載正在開發的擴展程序…”
  • 選擇插件目錄(D:\Download\JSONView-for-Chrome-master\WebContent);
  • 安裝完成,重新加載 (Ctrl+R)。
  • 測試地址:http://jsonview.com/example.json
餓了麼—Vue2.0高仿餓了麼核心模塊&移動端Web App項目爬坑

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

五、頁面骨架開發

移動端視口

  • index.html 中通過meta設置視口可被縮放,初試寬高設置
content="width=device-width,initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">

App.vue 中把頁面拆為三個區塊


header

tab

content


然後,分別抽成一個組件,引用 —— 所有組件自定義標籤名不可與html本身標籤重合 'v-header': header

移動端經典佈局 flex


商品

評論

商家




.tab
display: flex
width: 100%
height: 40px
line-height: 40px
.tab-item
flex:1
text-align: center



vueRouter

  1. Vue2.0 使用 進行【導航】
商品
  1. 【路由外鏈 】 —— 單頁面切換的內容頁,替換content div區塊

  1. main.js 中設置單頁面應用路由的【掛載組件】—— 默認App.vue 也可以自定義組件如layout.vue
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: ''
})

配置【路由map】:router->index.js


export default new Router({
mode: 'history',
routes: [
{
path: '/',
redirect: '/goods',//默認頁面重定向
},
{
path: '/goods',
component: goods
},
{
path: '/ratings',
component: ratings
},
]
})


  1. 【點擊】高亮顯示 a.router-link-active


#app .tab .tab-item>a{
display: block;
font-size: 14px;

color: rgb(77, 85, 93);
}
#app .tab .tab-item>a.router-link-active{
color: rgb(240, 20, 20)
}

1像素border實現

  • 錯誤做法:直接給tab加1像素邊框 X
.tab{
border-bottom: 1px solid rgba(7,17,27,0.1)
}
  • 問題是:這段代碼在PC端顯示,是1像素,但是在手機端顯示,就不是1像素。
  • 因為手機端有一個DPR的概念:它的物理像素是設備像素的兩倍。所以iPhone6上面可能就是一個2像素的邊框

【PC開發中用手機實時預覽的小技巧】

  • 新開一個gitbash,mac環境下輸入命令:ifconfig, windows下輸入:ipconfig
  • 獲知本機IP地址192.168.1.1,替換掉localhost(表示本機)
  • 將地址http://192.168.1.1:8080/goods#/輸入到草料二維碼網站中,生成二維碼,用手機掃描,即可查看
  • 必須與PC使用的是同一局域網
  • 正確做法:給 tab 加一個偽類:after , 讓它是一條1像素的線,然後在DBR為2或3的手機端縮放 √

  1. 定義mixin.styl: 通過css預處理器的函數方法,實現偽類線


border-1px($color)
position: relative
&:before
display: block
position: absolute
left:0
top: 0
width: 100%
border-bottom: 1px solid $color
content: ''
&:after
display: block
position: absolute
left:0
bottom: 0
width: 100%
border-top: 1px solid $color
content: ''


  1. 定義base.styl: 實現不同DBR的移動端的縮放
@media(-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5)//DPR為1.5的縮放0.7倍
.border-1px
&:before
-webkit-transform: scaleY(0.7)
transform:scaleY(0.7)
&:after
-webkit-transform: scaleY(0.7)
transform:scaleY(0.7)
@media(-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2)//DPR為2的縮放0.5倍
.border-1px
&:before
-webkit-transform: scaleY(0.5)
transform:scaleY(0.5)
&:after
-webkit-transform: scaleY(0.5)
transform:scaleY(0.5)


  1. 定義index.styl: 引用所有styl文件,最後在 main.js 中全局引用
@import"./mixin" //import後無空格
@import"./icon"
@import"./base"
main.js: import '@/common/stylus/index.styl' //import後有空格

六、header組件開發

vue-resource

  1. 安裝 vue-resource
  2. 1
  3. npm install vue-resource --save
  4. 注意:每次install完插件等之後需要重新啟動項目
  5. main.js 文件中:
import VueResource from 'vue-resource'
Vue.use(VueResource)
  1. 之後就可以在項目任何地方:使用 this.$http 命令
  2. App.vue 組件中:
  • export module{} 外:
const ERR_OK = 0; //定義常量,增強程序可讀性
  • export module{} 內:
data() {
return {
seller:{} //維護數據 seller
}
}
  • 異步請求數據,返回的是Promise對象


created: function () {
this.$http.get('/api/seller') //發送get請求,
.then(function(res){ //.then方法 請求完成後調用
//第一個函數是請求成功後方法
}, function (err) { //第二個函數是請求失敗後方法
})
}


  • 使用ES6 箭頭函數:箭頭函數前後必須有空格


created: function () {
this.$http.get('/api/seller')
.then((res) => {
res = res.body //拿到response返回的promise對象的body(Data Object)
if (res.errno === ERR_OK) {
this.seller = res.data;
//console.log(this.seller)
}
}, (err) => {
})
}


外部組件

  • 父組件 App.vue 中
    組件標籤中用v-bind綁定seller屬性,傳給子組件seller數據

  • 子組件 header.vue 中通過 props屬性 獲取父組件傳來的seller數據
props: {
seller: {
type: Object
}
}
  • 模板對應位置 顯示 對應seller.xxx 子數據
  1. 標籤: 使用seller數據圖片地址,v-bind綁定src屬性
 :class="lazy" data-original="seller.avatar"
  1. 文本內容: 雙向數據綁定顯示seller數據
{{seller.name}}
  1. 如果要獲取的是 seller數據對象的 子對象數組的 某一項:因為異步獲取數據,子對象可能為undefined,需要先 v-if 判斷是否存在


{{seller.supports[0].description}}

  1. 定義 classMap數組,通過獲取seller數據中的索引值,應用對應索引的class
created (){
this.classMap = ['decrease','descount','guarantee','invoice','special']
}

  1. mixin.styl 文件中偽函數:實現圖片在不同DPR下引用不同的圖片路徑
bg-image($url)
background-image: url($url+"@2x.png")
@media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3)
background-image: url($url+"@3x.png")
  • 公告內容 —— 文字【省略號效果】
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
  • 背景圖片【模糊濾鏡效果】


.background
position: absolute
top: 0
left: 0
width: 100%
height: 100%
z-index: -1
filter: blur(10px)


詳情層頁

  • 實現彈出層
  1. v-show指令 —— 控制彈出層的顯示/隱藏

data () {
return {
detailShow: false //通過改變數據detailShow 的true/false,控制元素的顯示/隱藏
}
}
  1. @click —— 觸發點擊事件,執行顯示函數

methods: {
showDetail () {
this.detailShow = true;
}
}

【Css Sticky footers佈局】

  • Sticky footers設計:
  1. 如果頁面內容不夠長的時候,頁面塊粘貼在視窗底部;
  2. 如果內容足夠長時,頁面塊會被內容向下推送(區別於fixed)
  • 相對複雜但兼容性最好的一個方案:
  1. 套路佈局
  2. //外層wrapper,min-height: 100%
  3. //內容層 padding-bottom: 64px
  • //要適應內容顯示的關閉按鈕 margin-top: -64px
  • 關鍵:padding-bottom撐開一個高度,為關閉按鈕留下位置



  • .detail-main
  • margin-top: 64px
  • padding-bottom: 64px
  • 樣式:
  • + View Code
  • Star組件抽象

    • 目標:為了增強擴展性,使足夠靈活
    • 思路:
    1. v-for —— 根據分數 遍歷itemClasses 顯示星星樣式



    :key="itemClass.value"
    :class="itemClass"
    class="star-item">



    1. props —— 從父組件接收兩個參數:size尺寸,score分數


     props:{
    size: {
    type: Number
    },
    score: {
    type: Number
    }
    }


    1. :class —— 綁定動態class, 在不同的調用地方, 可以設置不同的樣式
    2. View Code
    3. computed —— 根據size 計算出動態的class;根據score push對應個數的全亮星星class;判斷如果有半分或不足5分的,push進半星class和灰色星class;根據數組中對應的class顯示對應的星星圖片


    const LENGTH = 5;
    const CLS_ON = 'on';
    const CLS_HALF = 'half';
    const CLS_OFF = 'off';
    computed: {
    starType() {
    return 'star-' + this.size; //根據size 計算出動態的class
    },
    itemClasses() {
    let result = [];
    let score = Math.floor(this.score*2)/2;
    let hasDecimal = score % 1 !== 0;
    let integar = Math.floor(score);
    for(let i=0; i result.push(CLS_ON) //根據score 在itemClasses中push進對應個數的全亮星星class
    }
    if(hasDecimal) {
    result.push(CLS_HALF);//判斷如果有半分或不足5分的,push進半星class和灰色星class
    }
    while (result.length < LENGTH) {
    result.push(CLS_OFF)
    }
    return result; //根據itemClasses中對應的class顯示對應的星星圖片
    }
    }


    • 樣式: 除了通用樣式,還有根據不同size計算出的全部class的樣式

    小標題自適應線

    • 避免:寫死百分比,這樣寬屏幕會間隔很大,窄屏幕間隔會幾乎看不到
    • flex佈局:


    優惠信息




    .title
    display: flex
    width: 80%
    margin: 30px auto 24px auto
    .line
    flex: 1
    position: relative
    top: -6px
    border-bottom: 1px solid rgba(255, 255, 255, 0.2)
    .text
    padding: 0 12px
    font-size: 14px


    分享到:


    相關文章: