Vue2.0高仿餓了麼核心模組&移動端Web App專案爬坑(一)
原文https://www.cnblogs.com/ljq66/p/9980372.html
前言:學習Vue.js高仿餓了麼課程過程中,總結了這個Web App專案從準備到開發完畢自己覺得很重要的知識點。這一篇主要介紹:專案準備、頁面骨架開發、header元件開發。
專案github地址:https://github.com/66Web/ljq_eleme,歡迎Star。
App | header |
一、專案分析&學習目標 |
當前最火的MVVM框架
- Vue.js —— 輕量、簡潔、高效、資料驅動、元件化
高仿上線外賣App標準來開發
- 核心模組 —— 商家模組
開發一個webApp的全流程
- 需求分析
- 腳手架工具
- 資料mock
- 架構設計
- 程式碼編寫
- 自測
- 編譯打包
以線上生產環境的程式碼質量作標準
- 程式碼開發及測試環節:
- UI標註
- 真實資料演示
- 程式碼規範
- 架構設計
- 元件抽象
- 模組拆分
- 程式碼風格統一
- JS變數命名規範
- 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程式碼風格檢查工具
- 工程化 元件化 模組化
- 移動端常用開發技巧:
- flex彈性佈局
- css stickyfooter
- 酷炫的互動設計
二、Vue.js介紹 |
近年來前端開發趨勢
- 舊瀏覽器逐漸淘汰,移動端需求增加
- 前端互動越來越多,功能越來越複雜
- 架構從傳統後臺MVC 向REST API+ 前端MV* 遷移
(前者傳統MVC:更新資料會重新整理頁面 後者前端MV*: 向後端REST API非同步請求資料,區域性重新整理頁面)
MV* —— MVC、MVP、MVVM
MVVM框架
View ViewModel Model
檢視 通訊 資料
- DOM 觀察者 Javascript物件
- 針對具有複雜互動邏輯的前端應用
- 提供基礎的架構抽象
- 通過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 |
|
1 |
|
專案檔案
- src資料夾:存放專案原始碼
- bulld目錄+ config目錄:webpack配置相關
- node_modules資料夾:npm install 安裝的依賴程式碼庫
- static—>.gitkeep: 當這個目錄為空時也可以將它提交到git倉庫中
- babelrc : babel的一些配置,es6語法的轉換
- .editorconfig: 編輯器的配置
- .eslintignore: 忽略語法檢查的目錄檔案,一般忽略build目錄和node_modules目錄
- .eslintrc.js: eslint的配置檔案
- gitignore: 上傳git倉庫要忽略的一些檔案的配置
- index.html: 入口html檔案,要使用的css和js檔案會在編譯過程中自動插入
- package.json:整個專案的配置檔案,一般用來描述專案 ↓
→ scripts: 配置一些需要執行的命令
→ dependencies:開發環境中的依賴
→ devdependencies: 編譯過程中的依賴
專案執行
1 |
|
- src開發目錄下:
- main.js —— 專案入口檔案
- App.vue —— 主頁面元件
- vue語法糖: export default { } 一個物件——可以定義一個元件
【小知識點】sublime自動格式化 —— Command+option+L 或 Control+alt+L |
- 在父元件中使用子元件,如Hello.vue:
- 引用
import Hello from './compoments/Hello'
- 註冊
export default{ components: { Hello //es6語法 相當於 'Hello': Hello } }
- 使用標籤
<hello><hello>
開發時的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語法】
|
- resource目錄下:專案圖片檔案——可以刪掉無用的assets目錄,但需要修改引用到的地方
- components目錄下:佈局、業務功能等分模組管理元件,如header目錄,footer目錄
- static->css目錄下:reset.css 標籤預設樣式
- 在 index.html 中引入:
<link rel="stylesheet" type="text/css" href="static/css/reset.css">
前後端分離
- Vue SPA —— 前端通過 vue-resource Ajax從後端獲取資料
- 前端最重要的任務:mock資料(後臺資料模擬) data.json
{ "seller":{} //商家相關欄位 "goods":{} //商品相關欄位 "rattings":{} //評論相關欄位 }
webpack.dev.conf.js中配置
- 使用 express框架 開啟一個node server,用 express.Router 編寫這些介面請求
- 首先:在 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物件
- 然後:找到 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安裝第三方外掛】
—— 轉載自【小白白打醬油部落格】 |
五、頁面骨架開發 |
移動端視口
- index.html 中通過meta設定視口可被縮放,初試寬高設定
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
App.vue 中把頁面拆為三個區塊
<div id="app"> <div class="header">header</div> <div class="tab">tab</div> <div class="content">content</div> </div>
然後,分別抽成一個元件,引用 —— 所有元件自定義標籤名不可與html本身標籤重合 'v-header': header
移動端經典佈局 flex
<div class="tab"> <div class="tab-item">商品</div> <div class="tab-item">評論</div> <div class="tab-item">商家</div> </div>
.tab display: flex width: 100% height: 40px line-height: 40px .tab-item flex:1 text-align: center
vueRouter
- Vue2.0 使用 <router-link> 進行【導航】
<router-link :to="{ path: '/goods' }">商品</router-link>
- 【路由外鏈 】<router-view> —— 單頁面切換的內容頁,替換content div區塊
<router-view></router-view>
-
main.js 中設定單頁面應用路由的【掛載元件】—— 預設App.vue 也可以自定義元件如layout.vue
/* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '<App/>' })
-
配置【路由map】:router->index.js
export default new Router({ mode: 'history', routes: [ { path: '/', redirect: '/goods',//預設頁面重定向 }, { path: '/goods', component: goods }, { path: '/ratings', component: ratings }, ] })
- 【點選】高亮顯示 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開發中用手機實時預覽的小技巧】
|
- 正確做法:給 tab 加一個偽類:after , 讓它是一條1畫素的線,然後在DBR為2或3的手機端縮放 √
<div class="tab border-1px">
- 定義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: ''
- 定義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)
- 定義index.styl: 引用所有styl檔案,最後在 main.js 中全域性引用
@import"./mixin" //import後無空格 @import"./icon" @import"./base"
main.js: import '@/common/stylus/index.styl' //import後有空格
六、header元件開發 |
vue-resource
- 安裝 vue-resource
1
npm
install
vue-resource --save
注意:每次install完外掛等之後需要重新啟動專案
- main.js 檔案中:
import VueResource from 'vue-resource' Vue.use(VueResource)
之後就可以在專案任何地方:使用 this.$http 命令
- 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 中 <header> 元件標籤中用v-bind繫結seller屬性,傳給子元件seller資料
<v-header :seller="seller"></v-header>
- 子元件 header.vue 中通過 props屬性 獲取父元件傳來的seller資料
props: { seller: { type: Object } }
- 模板對應位置 顯示 對應seller.xxx 子資料
- <img>標籤: 使用seller資料圖片地址,v-bind繫結src屬性
:src="seller.avatar"
- 文字內容: 雙向資料繫結顯示seller資料
{{seller.name}}
- 如果要獲取的是 seller資料物件的 子物件陣列的 某一項:因為非同步獲取資料,子物件可能為undefined,需要先 v-if 判斷是否存在
<div class="support" v-if="seller.supports"> <span class="icon" :class="this.classMap[seller.supports[0].type]"></span> <span class="text">{{seller.supports[0].description}}</span> </div>
- 定義 classMap陣列,通過獲取seller資料中的索引值,應用對應索引的class
created (){ this.classMap = ['decrease','descount','guarantee','invoice','special'] }
<span class="icon" :class="this.classMap[seller.supports[0].type]"></span>
- 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)
詳情彈層頁
- 實現彈出層
- v-show指令 —— 控制彈出層的顯示/隱藏
<div class="detail" v-show="detailShow"></div>
data () { return { detailShow: false //通過改變資料detailShow 的true/false,控制元素的顯示/隱藏 } }
- @click —— 觸發點選事件,執行顯示函式
<div class="bulletin-wrapper" @click="showDetail>
methods: { showDetail () { this.detailShow = true; } }
【Css Sticky footers佈局】
|
Star元件抽象
- 目標:為了增強擴充套件性,使足夠靈活
- 思路:
- v-for —— 根據分數 遍歷itemClasses 顯示星星樣式
<div class="star" :class="starType"> <span v-for="itemClass in itemClasses" :key="itemClass.value" :class="itemClass" class="star-item"> </span> </div>
- props —— 從父元件接收兩個引數:size尺寸,score分數
props:{ size: { type: Number }, score: { type: Number } }
- :class —— 繫結動態class, 在不同的呼叫地方, 可以設定不同的樣式
View Code
- 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<integar; 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佈局:
<div class="title"> <div class="line"></div> <div class="text">優惠資訊</div> <div class="line"></div> </div>
.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
【Postcss工具】
|
過渡動畫元件 transition
<transition name="fade"> <div class="detail"> </transition>
.detail opacity: 1 background: rgba(7, 17, 27, 0.8) &.fade-enter-active, &.fade-leave-active transition: all 0.5s ease &.fade-enter, &.fade-leave-active opacity: 0 background: rgba(7, 17, 27, 0)
iPhone手機背景模糊效果
backdrop-filter: blur(10px) // PC端和其它手機看不出效果