VUE高仿餓了麼appjavascript
剛學習了VUE高仿餓了麼app課,記錄課的要點,鞏固知識。css
Vue.js 是一個用於建立 web 交互界面的。其特色是html
簡潔 HTML 模板 + JSON 數據,再建立一個 Vue 實例,就這麼簡單。 數據驅動 自動追蹤依賴的模板表達式和計算屬性。 組件化 用解耦、可複用的組件來構造界面。 輕量 ~24kb min+gzip,無依賴。 快速 精確有效的異步批量 DOM 更新。 模塊友好 經過 NPM 或 Bower 安裝,無縫融入你的工做流。
借用express + data 構建擬後臺vue
vue 1.0 express vue.router vue.rescrouse better-scroll less
設備上像素 = 樣式像素 * 設備縮放比例java
屏幕寬度 320px 480px 640px 縮放比例 1 1.5 2
當樣式像素必定時,因手機有320px,640px等.各自的縮放比差別,因此設備顯示像素就會有1Npx,2Npx.爲保設計稿還原度,解決就是用media + scale.web
.border(@borderColor){ position: relative; &::after{ content: ""; position: absolute; bottom: 0; left: 0; width: 100%; border-top: 1px solid @borderColor; } } @media (min-device-pixel-ratio: 1.5) { .border{ &::after{ transform: scaleY(0.7); } } } @media (min-device-pixel-ratio: 2) { .border{ &::after{ transform: scaleY(0.5); } } }
經過查詢它的縮放比,在媒體寬爲1.5倍時, round(1px 1.5 / 0.7) = 1px 在媒體寬爲2倍時, round(1px 2 / 0.5) = 1px.express
在商品路由中,導航寬度固定80px的,由於手機分辨率大小不一,因此食物詳情自適應.解決就是flex佈局.json
api
css
<style type="text/less"> .food{ display: flex; width: 100%; .nav{ flex: 0 0 80px; width: 80px; } .foodList{ flex: 1; } } </style>
html數組
<div class="food"> <section class="nav"></section> <section class="foodList"></section> </div>
在父元素設彈性佈局,導航裏設彈性爲0,定寬爲80px.商品食物詳情彈性爲1.就適應寬度變化.
作商家彈出頁時,信息高度是沒法預約的,有可能溢出window高度,也可能少於window高度,但底部按鈕,當信息高度少於window高度,要固定在底部40px.解決就是用sticky footer佈局
css
<style type="text/less"> .showDetil{ position: absolute; width: 100%; height: 100%; .sellerDetil{ width: 100%; min-height: 100%; padding-bottom: 40px; } .btn{ position: relative; top: -40px; height: 40px; } } </style>
html
<div class="showDetil"> <section class="sellerDetil"></section> <section class="btn"></section> </div>
父元素高相同window高,信息最小高就相同window高,按鈕這時就溢出了.
再設置底的填充,底內邊距高就是按鈕的高. 按鈕在用相對定位,定在信息的底填充裏.
因信息最少高度是100%,所按鈕要不釘在底部了.要不溢出.
在食物彈出頁.設計圖食物圖的寬高是相等,每張圖的寬高比例有可能有區別,但也要作自適應.解決就是用padding邊距.
css
<style type="text/less"> .imgs{ width: 100%; height: 0; position: relative; padding-top: 100%; .image{ position: absolute; top: 0; width: 100%; height: 100%; left: 0; } } </style>
html
<div class="imgs"> <img src="..." class="image"> </div>
在父元素,邊距的長是取決去寬的,所其寬度與邊距的長是相等的.
在把高設爲0,寬爲100%,上邊距100%,上邊據就盒子的高.盒子是爲正形.
子元素設寬與高爲100%,那也是正形.
根據後臺輸出的數據,斷定顯示那個的圖標.這vue典型的數據.驅動.解決是使用:class困綁數據
html
<template> <ul> <li v-for="date in goods"> <span :class="classmap[date.type]"></span> </li> </ul> </template>
js
<script type="text/javascript"> export default{ data() { return { classmap: ['decrease', 'discount', 'guarantee', 'invoice', 'special'] }; } } </script>
css
<style type="text/less"> .bgimg(@imgs) { background-image: url('@imgs+".png"') 0 0 no-repeat ~'/' 100% 100%; } .decrease{ display: inline-block; height: 12px; width: 12px; .bgimg('decrease_3'); } .discount{ display: inline-block; height: 12px; width: 12px; .bgimg('discount_3'); } .guarantee{ display: inline-block; height: 12px; width: 12px; .bgimg('guarantee_3'); } .invoice{ display: inline-block; height: 12px; width: 12px; .bgimg('invoice_3'); } .special{ display: inline-block; height: 12px; width: 12px; .bgimg('special_3'); } </style>
經過v-for,遍歷數據,因此date.type獲得數據並判斷類型.而後通classmap數組斷定綁那個class.來加圖標.
點擊加食物時,觸動小球彈出的動畫,小球的落點是在車的中央.但起點是根各個節點位子而又差異的.解決使用transitions + events + dispatch事件冒泡
cartcontrol子組件
html
<template> <div class="cartcontrol"> <section class="cart-decrease" @click.stop.prevent="decreaseCart" v-show="food.count > 0" transition="move"></section> <section class="cart-count" v-show="food.count > 0">{{food.count}}</section> <section class="cart-add" @click.stop.prevent="addCart"> </section> </div> </template>
js
<script type="text/javascript"> export default { props: { food: { type: Object } }, methods: { addCart(event) { if (!this.food.count) { Vue.set(this.food, 'count', 1); this.food.count = 1; } else { this.food.count++; }; this.$dispatch('cart.add', event.target); }, decreaseCart() { if (this.food.count) { this.food.count--; }; } } }; </script>
在加食物,觸發了addCart事件,設用set方法給數據加屬性,並使cart.add事件冒泡出去,event.target做爲事件參數,即節點冒泡出去.
goods父組件
html
<template> <shop v-ref:shop :delivery-price="seller.deliveryPrice" :min-price="seller.minPrice" :select-foods="selectFoods"></shop> </template>
js
<script> export default { methods: { _drop(target) { this.$refs.shop.drop(target); } }, events: { 'cart.add'(target) { this._drop(target); } }, components: { shop, cartcontrol, food } }; </script>
在冒泡被events鉤子監聽,與觸動_drop方法,經過接口得到購物車組建的事件,就把control組建event.target傳入購物車組建的事件,及把control節點傳入了shop組建.
shop組建
html
<template> <div class="shopcart"> <section class="ball-container"> <div transition="drop" v-for="ball in balls" v-show="ball.show" class="ball"> <div class="inner inner-hook"></div> </div> </section> </div> </template>
js
<script type="text/javascript"> export default { data() { return { balls: [ { show: false }, { show: false }, { show: false }, { show: false }, { show: false }], dropBalls: [], fold: true }; }, methods: { drop(el) { for (var i = 0; i < this.balls.length; i++) { let ball = this.balls[i]; if (!ball.show) { ball.show = true; ball.el = el; this.dropBalls.push(ball); return; }; }; } }, transitions: { drop: { beforeEnter(el) { let count = this.balls.length; while (count--) { let ball = this.balls[count]; if (ball.show) { let rect = ball.el.getBoundingClientRect(); let x = rect.left - 32; let y = -(window.innerHeight - rect.top - 22); el.style.display = ''; el.style.transform = `translate3d(0,${y}px,0)`; let inner = el.getElementsByClassName('inner-hook')[0]; inner.style.transform = `translate3d(${x}px,0,0)`; } } }, enter(el) { let rf = el.offsetHeight; this.$nextTick(() => { el.style.transform = 'translate3d(0,0,0)'; let inner = el.getElementsByClassName('inner-hook')[0]; inner.style.transform = 'translate3d(0,0,0)'; }); }, afterEnter(el) { let ball = this.dropBalls.shift(); if (ball) { ball.show = false; el.style.display = 'none'; }; } } }, components: { cartcontrol } }; </script>
傳入節點數據,過渡執行前可插入一個beforeEnter事件,通getBoundingClientRect定位.動畫執行後可插入一個afterEnter,還原小球
與後臺的配合,經過插vue.resource + express 鏈接獲得數據
dev-server
<script type="text/javascript"> import express from 'express'; var app = express(); var appData = require('../data.json'); var seller = appData.seller; var goods = appData.goods; var ratings = appData.ratings; var apiRoutes = express.Router(); apiRoutes.get('/seller', function (req, res) { res.json({ errno: 0, data: seller }); }); apiRoutes.get('/goods', function (req, res) { res.json({ errno: 0, data: goods }); }); apiRoutes.get('/ratings', function (req, res) { res.json({ errno: 0, data: ratings }); }); app.use('/api', apiRoutes); </script>
經過與配和框架express,連到數據。並放在api裏.
main.js
import VueResource from 'vue-resource'; Vue.use('VueResource');
引進插件和使用,在全局也可使用.
header組建
<script type="text/javascript"> export default{ created() { this.$http.get('/api/ratings').then((response) => { var response = response.body; if (response.errno === 0) { this.ratings = response.data; }; }); } } </script>
在框架的鉤子,及建立就經過http.get連到express發的數據,通參數response獲得.body表示數據以json格式響應.注意接收數據是異步實現,若是出報錯undefined,可用v-if判斷,當獲取數據後在渲染.
用戶的滿意度有,推薦與吐槽再加上所有,就三個分頁,分頁經過按鈕切換.如何製做呢?解決是使用v-show進判斷.
ratingselect子組件
html
<template> <div class="ratingselect"> <div class="rating-type"> <span @click="select(2, $event)" class="block positive" :class="{'active':selectType === 2}">{{desc.all}}<span class="count">{{ratings.length}}</span></span> <span @click="select(0, $event)" class="block positive" :class="{'active':selectType === 0}">{{desc.positive}}<span class="count">{{positives.length}}</span></span> <span @click="select(1, $event)" class="block negative" :class="{'active':selectType === 1}">{{desc.negative}}<span class="count">{{negatives.length}}</span></span> </div> <div @click="toggleContent" class="switch" :class="{'on':onlyContent}"> <span class="iconfont arre"></span> <span class="text">只看有內容的評價</span> </div> </div> </template>
js
<script type="text/javascript"> export default{ props: { ratings: { type: Array, default() { return []; } }, selectType: { type: Number, default: 2 }, onlyContent: { type: Boolean, default: true }, desc: { type: Object, default() { return { all: '所有', positive: '滿意', negative: '不滿意' }; } } }, methods: { select(type) { this.selectType = type; this.$dispatch('ratingtype.select', type); }, toggleContent() { this.onlyContent = !this.onlyContent; this.$dispatch('ratingtype.toggleContent', this.onlyContent); } } }; </script>
滿意是爲:0,不滿意是爲:1,所有是爲:2.
因在點擊切換按鈕,觸發方法,經過傳入參數來替換數據,數據selectType賦值等於參數.參數是自義定,然而能夠在參數下功夫,然用冒泡將數據分出.
food父組件
html
<template> <transiton> <div class="rating"> <h4 class="title">商品評價</h4> <ratingselect :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect> <div class="rating-wrapper"> <ul v-show="food.ratings && food.ratings.length"> <li v-for="rating in food.ratings" v-show="needShow(rating.rateType,rating.text)" class="rating-item"> <div class="user"> <span class="name">{{rating.username}}</span> <img class="avatar" width="12" height="12" :src="rating.avatar"> </div> <div class="time">{{rating.rateTime | formatDate}}</div> <p class="text"> <span v-if="rating.rateType === 0" class="arrowUp iconfont"></span> <span v-if="rating.rateType === 1" class="arrowDown iconfont"></span>{{rating.text}} </p> </li> </ul> <div class="no-rating" v-show="!food.ratings || !food.ratings.length">暫無評價</div> </div> </div> </transiton> </template>
js
<script type="text/javascript"> import Vue from 'vue'; import ratingselect from 'components/ratings/ratingselect'; const POSITIVE = 0; const NEGATIVE = 1; const ALL = 2; export default { data() { return { showFlage: false, selectType: ALL, onlyContent: true, desc: { all: '所有', positive: '推薦', negative: '吐槽' } }; }, methods: { needShow(type, text) { if (this.onlyContent && !text) { return false; } if (this.selectType === ALL) { return true; } else { return type === this.selectType; } } }, events: { 'ratingtype.select'(type) { this.selectType = type; this.$nextTick(() => { this.scroll.refresh(); }); }, 'ratingtype.toggleContent'(onlyContent) { this.onlyContent = onlyContent; this.$nextTick(() => { this.scroll.refresh(); }); } }, components: { ratingselect } }; </script>
在事件鉤子上,實行監聽,把冒泡觸發並賦值,數據就獲得.在遍歷數據,用v-show進行判斷.
只在v-if,v-show,v-for觸動節點的變更效果
當 show 屬性改變時,Vue.js 將相應地插入或刪除元素,按照以下規則改變過渡的 CSS 類名:
若是 show 變爲 false,Vue.js 將:
調用 beforeLeave 鉤子;
添加 v-leave 類名到元素上以觸發過渡;
調用 leave 鉤子;
等待過渡結束(監聽 transitionend 事件);
從 DOM 中刪除元素並刪除 v-leave 類名;
調用 afterLeave 鉤子。
若是 show 變爲 true,Vue.js 將:
調用 beforeEnter 鉤子;
添加 v-enter 類名到元素上;
把它插入 DOM;
調用 enter 鉤子;
強制一次 CSS 佈局,讓 v-enter 確實生效。而後刪除 v-enter 類名,以觸發過渡,回到元素的原始狀態;
等待過渡結束;
調用 afterEnter 鉤子。
節點溢滿時,是設計稿沒有滾動條的,要上下移動.解決使用better-scroll插件.
html
<div class="sellerx" v-el:seller style="overflow: hidden;"> <div class="seller-content"></div> </div>
js
<script type="text/javascript"> ready() { this.$nextTick(() => { if (!this.scroll) { this.scroll = new Bscroll(this.$els.seller, { click: true }); } else { this.scroll.refresh(); } }); }, </script>
但父元素設置溢出隱藏,可用插件的移動顯出子節點超的內容.要在節點放個接口,使用框架鉤子,建立betterScroll事例,那藏的內容通立體相上下移.better-scroll是調用樣式的translate是子節點上下引動.
經過引入樣式,有是會錯誤.解決使用設置標籤
<style type="text/less"></style>
處理器會識別到標籤的樣式類別,編譯樣式.
在使用eslint語法校驗時,常常報錯,但能夠在eslintrc設置進行忽略.
是把標籤縮進與空格捆和使用,解決是可用tab代替空格.
'indent': 0,
'space-before-function-paren': 0
設置縮進空行.
可在前加註銷
/ eslint-disable no-unused-vars /
要靈活的用vue,先要處理好數據的邏輯.
然而要懂得基本的數據傳遞屬性.
子組件傳給父組件- 能夠用接口ref;也能夠子組件的冒泡把數據傳去,父組件用鉤子events監聽並接到數據. 父組件傳給子組件- 能夠在子組件props鉤子,接收父組件的傳遞.也能夠父組件用ref接口調用子組件的方法,並把數據傳入方法去.
實戰是最重要.