(給達達前端加星標,提高前端技能)javascript
開發一款vue.js開發一款app,使用vue.js是一款高效的mvvm框架,它輕量,高效,組件化,數據驅動等功能便於開發。使用vue.js開發移動端app,學會使用組件化,模塊化的開發方式。php
學習瞭如何根據需求分析開發,使用腳手架工具,數據mock,架構設計,本身測試,編譯打包等流程。css
線上生產環境,如何考慮架構設計,組件抽象,模塊拆分,代碼風格統一,變量命名要求規範等優勢。html
一款外賣app,商家頁面,商家基本信息(頂部),商品區塊,商品列表,分類列表,小球飛入購物車的動畫。商品詳情頁,須要有頂部商品的大圖,商品的詳細信息,以及還有商品的評價列表。前端
商品,評論列表,商家展現商家的詳情信息。vue
用vue-resource與後端作數據交互,vue-router前端路由,better-scroll的Js庫等。使用vue-cli腳手架,搭建基本代碼框架,vue-router官方插件管理路由。vue-resource是用於ajax通訊的,webpack構建工具的使用。java
Vue是一套用於構建用戶界面的漸進式JavaScript框架。與其它大型框架不一樣的是,Vue 被設計爲能夠自底向上逐層應用。Vue 的核心庫只關注視圖層,方便與第三方庫或既有項目整合。node
Vue.js 的目標是經過儘量簡單的 API 實現響應的數據綁定和組合的視圖組件,Vue.js 自身不是一個全能框架——它只聚焦於視圖層。所以它很是容易學習,很是容易與其它庫或已有項目整合。webpack
目錄/文件nginx
說明
build
項目構建(webpack)相關代碼
config
配置目錄,包括端口號等。咱們初學可使用默認的。
node_modules
npm 加載的項目依賴模塊
src
包含了幾個目錄及文件:
static
靜態資源目錄,如圖片、字體等。
test
初始測試目錄,可刪除
.xxxx文件
這些是一些配置文件,包括語法配置,git配置等。
index.html
首頁入口文件,你能夠添加一些 meta 信息或統計代碼啥的。
package.json
項目配置文件。
README.md
項目的說明文檔,markdown 格式
說一說mvc和mvvm的區別
mvc的全名是Model view Controller,是模型model,視圖view,控制器controller的縮寫,用一種業務邏輯,數據,界面顯示分離的方法來寫代碼,view視圖,視圖層調用控制器到controller控制器,控制器調用model,model返回數據給控制器,而後控制器將數據返回給view。
這是mvc的簡單調用流程,mvc模式是單向的數據綁定,view視圖層調用model層,要經過中間層controller來實現。
mvvm模式是雙向數據綁定,view,model,vm進行數據的綁定和事件的監聽,對view和model進行監聽,當有一方的值發生變化時,就更新另外一個。
數據響應原理
組件化原理
vue-cli,vue.js的開發利器,腳手架
vue-cli能夠搞定,目錄結構,本地調試,代碼部署,熱加載,單元測試。
vue-cli的安裝方法:
node -v
mac
sudo npm install -g vue-cli
使用webpack模板,名字sell,外賣app。
運行效果:
而後把項目放進你的編輯器
mode_modules文件夾:npm install 安裝的依賴代碼庫
src文件夾是咱們存放的源碼
這個文件跟我不同也沒事。
editorconfig是編輯器的配置
eslintignore爲忽略語法檢查的目錄文件
eslintrc.js爲eslint的配置文件
商品頁面:
商品頁_公共以及優惠信息
商品頁購物車詳情
商品頁面_商品詳情頁面
評價頁
商家頁
設備像素比devicePixelRatio
在移動端,devicePixelRatio指的是window.devicePixelRatio。
移動端設備分爲非視網膜屏幕和視網膜屏幕。
window.devicePixelRatio是設備上物理像素和設備獨立像素的比例,公式表就是:window.devicePixeRatio = 物理像素/dips。
icomoon.io,圖標字體制做
mock數據,模擬後臺數據
icon- 開頭的圖標(如圖所示)
首先進入網頁https://icomoon.io/
而後點擊右上角的「IcoMoon APP」按鈕,選擇導入本身的SVG圖來生成ico-的圖標,點擊新頁面左上角的「Inport ICONS」。
在devServer下面加入
頁面骨架開發
sell->build->confi->node_modules->resource, img, psd, svg ->src, common->components, app.vue->static
<html> <head> <meta charset="utf-8"> <title>sell</title> <meta name="viewport" content="width=device-width,initial-scale=1.0,maxinum-scale=1.0, minimun-scale=1.0,user-scalable=no"> <link rel="stylesheet" type="text/css" href="static/css/reset.css"> </head> </body> <app></app> </body> </html>
meta name="viewport"
它是移動端瀏覽器在一個比屏幕更寬的虛擬窗口中渲染頁面,用來實現展現沒有作移動端適配的網頁,能夠完整的展現給用戶,viewport的寬度就是可顯示區域的寬度。
<meta name="viewport" content="width=device-width,initial-scale=1.0,maxinum-scale=1.0, minimun-scale=1.0,user-scalable=no">
這些屬性能夠混合使用,width控制視圖窗口的寬度,height控制視圖窗口的高度,這個屬性不多用,initial-scale爲控制頁面最初加載時在最理想的狀況下縮放的等級,一般設置爲1.0,能夠是小數,maximum-scale爲容許用戶的最大縮放量,minimum-scale爲容許用戶的最小縮放量。
user-scalable爲是否容許用戶進行縮放,值只能「no」或者「yes」。no爲不容許,yes爲容許。
width和initial-scale設置了二者,瀏覽器會自動選擇數值最大的進行適配。
就是當窗口的最適配理想寬度爲300時,initial-scale的值設置爲1時,width設置的值爲400,那麼取最大值,400。
當窗口的最適配理想值爲500時,那麼取的值爲500。
width=device-width和initial-scale=1都表示爲最理想的viewport,可是在ipad,iphone等移動設備,ie上,橫豎屏不分,默認都爲豎屏的寬度,兼容的最好寫法。
什麼是viewport,它是用戶網頁的可視區域,翻譯就是視區。
手機瀏覽器是把頁面放在一個虛擬的"窗口"(viewport)中,一般這個虛擬的"窗口"(viewport)比屏幕寬,這樣就不用把每一個網頁擠到很小的窗口中(這樣會破壞沒有針對手機瀏覽器優化的網頁的佈局),用戶能夠經過平移和縮放來看網頁的不一樣部分。
沒有添加viewport的效果:
加了viewport的效果:
viewport這個特性被用於移動設備,可是也能夠用在支持相似「固定到邊緣」等特性的桌面瀏覽器,如微軟的edge。
按百分比計算尺寸的時候,就是參照的初始視口,它指的是任何用戶代理和樣式對它進行修改以前的視口。桌面瀏覽器若是不是全屏模式的話,通常是基於窗口大小。
在移動設備上,初始視口一般就是應用程序可使用的屏幕部分。
在viewport中就是瀏覽器上用來顯示網頁的那部分區域。
width=device-width能使全部瀏覽器當前的viewport寬度變成理想的寬度,initial-scale=1是將頁面的初始縮放值設置爲1。用來將viewport的寬度變成爲理想的寬度,防止橫向滾動條出現。
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0">
width=device-width表示爲寬度是設備屏幕的寬度
initial-scale=1.0表示爲初始的縮放比例
minimum-scale=0.5表示爲最小的縮放比例
maximum-scale=2.0表示爲最大的縮放比例
user-scalable=yes表示用戶是否能夠調整縮放比例
設備像素,設備獨立像素,css像素掌握
設備像素就是屏幕上的真實像素點,iphone6的設備像素像素爲750*1334,則屏幕上有750*1334個像素點;設備獨立像素,操做系統定義的一種長度單位,iphone6的設備獨立像素375*667,正好是設備像素的一半,css像素,css中的長度單位,在css中使用px都是指css像素。
物理像素來表明設備像素,獨立像素表明設備獨立像素。
在很早的時候,只有物理像素,沒有獨立像素,在不縮放的前提,css中的1px表明着一個物理像素。
不過從iphone4開始,推出了retina屏幕,物理像素變成640*960,屏幕尺寸沒有變化,在單位面積上的物理像素的數量增長了,則表示屏幕密度增長了。按照原來,1px css像素由1個物理像素來渲染,那麼width:320px的元素就會佔據半個屏幕的寬度。
1個獨立像素==2個物理像素
viewport是瀏覽器窗口,表明瀏覽器的可視區域,就是瀏覽器中用來顯示網頁的部分區域。
像素單位有設備像素,邏輯像素,css像素。
設備像素也叫物理像素。
什麼是設備像素,它指的是顯示器上的真實像素,每一個像素的大小是屏幕固有的屬性。
設備分辨率是用來描述這個顯示器的寬和高分別有多少個設備像素。
設備像素和設備分辨率由操做系統來管理。
全局安裝vue-cli腳手架工具
cnpm install -g vue-cli
初始化sell項目
vue init webpack sell
進入sell目錄
cd sell
安裝依賴
cnpm install
運行項目
cnpm run dev 或者 node build/dev-server.js
寫mock數據接口
// 文件位置:build/dev-server.js // 注:此處是關鍵代碼 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({ error: 0, data: seller }) }) apiRoutes.get('/goods', function (req, res) { res.json({ error: 0, data: goods }) }) apiRoutes.get('/ratings', function (req, res) { res.json({ error: 0, data: ratings }) }) app.use('/api', apiRoutes)
項目實戰,頁面骨架開發
webstorm設置文件的默認結構
<template> </template> <script type="text/ecmascript-6"> export default {} </script> <style lang="stylus" rel="stylesheet/stylus"> </style>
安裝ajax異步請求插件vue-resource
cnpm install vue-resource --save-dev
文件位置:src/APP.vue
<template> <div> <v-header :seller="seller"></v-header> <div class="tab border-1px"> <div class="tab-item"> <router-link to="/goods">商品</router-link> </div> <div class="tab-item"> <router-link to="/ratings">評論</router-link> </div> <div class="tab-item"> <router-link to="/seller">商家</router-link> </div> </div> <!-- 路由外鏈 --> <keep-alive> <router-view :seller="seller"></router-view> </keep-alive> </div> </template> <script type="text/ecmascript-6"> import {urlParse} from './common/js/util'; import header from './components/header/header.vue'; const ERR_OK = 0; export default { data() { return { seller: { id: (() => { let queryParam = urlParse(); return queryParam.id; })() } } }, created() { this.$http.get('/api/seller?id=' + this.seller.id).then(response => { response = response.body; if (response.error === ERR_OK) { this.seller = Object.assign({}, this.seller, response.data); console.log(this.seller.id); } }, response => { }); }, components: { 'v-header': header } } </script> <style lang="stylus" rel="stylesheet/stylus"> @import "common/stylus/mixin.styl" .tab display: flex width: 100% height: 40px border-1px(rgba(7, 17, 27, 0.1)) line-height: 40px .tab-item flex: 1 text-align: center & > a display: block font-size: 14px color: rgb(77, 85, 93) &.active color: rgb(240, 20, 20) </style>
文件位置:src/router/index.js
import Vue from 'vue'; import Router from 'vue-router'; import goods from '@/components/goods/goods.vue'; import ratings from '@/components/ratings/ratings.vue'; import seller from '@/components/seller/seller.vue'; Vue.use(Router); const routes = [{ path: '/', component: goods }, { path: '/goods', component: goods }, { path: '/ratings', component: ratings }, { path: '/seller', component: seller }]; export default new Router({ linkActiveClass: 'active', routes: routes });
文件位置:src/main.js
import Vue from 'vue'; import App from './App.vue'; import router from './router'; import VueResource from 'vue-resource'; Vue.config.productionTip = false; import '../static/css/reset.css'; import './common/stylus/base.styl'; import './common/stylus/index.styl'; import './common/stylus/icon.styl'; Vue.use(VueResource); new Vue({ el: '#app', router, render: h => h(App) });
安裝better-scroll
cnpm install better-scroll --save-dev
export default { created() { this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']; this.$http.get('/api/goods').then(response => { response = response.body; if (response.error === ERR_OK) { this.goods = response.data; console.log(this.goods); this.$nextTick(() => { this._initScroll(); this._calculateHeight(); }) } }, response => { }); } }
export default { methods: { selectMenu(index, event) { if (!event._constructed) { return; } console.log(index); let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook'); let el = foodList[index]; this.foodsScroll.scrollToElement(el, 300); }, _initScroll() { this.menuScroll = new BScroll(this.$refs.menuWrapper, { click: true }); this.foodsScroll = new BScroll(this.$refs.foodsWrapper, { click: true, probeType: 3 }); this.foodsScroll.on('scroll', (pos) => { this.scrollY = Math.abs(Math.round(pos.y)); }) }, _calculateHeight() { let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook'); let height = 0; this.listHeight.push(height); for (let i = 0; i < foodList.length; i++) { let item = foodList[i]; height += item.clientHeight; this.listHeight.push(height); } } }, components: { shopcart, cartcontrol } }
Vue.set(this.food, 'count', 1);
小球動畫函數監聽
export default { methods: { drop(el) { for (let 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; } } }, beforeDrop: function (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.webkitTransform = `translate3d(0,${y}px,0)`; el.style.transform = `translate3d(0,${y}px,0)`; let inner = el.getElementsByClassName('inner-hook')[0]; inner.style.webkitTransform = `translate3d(${x}px,0,0)`; inner.style.transform = `translate3d(${x}px,0,0)`; console.log(el, x, y); } } }, dropping: function (el, done) { let rf = el.offsetHeight; this.$nextTick(() => { el.style.display = ''; el.style.webkitTransform = 'translate3d(0,0,0)'; el.style.transform = 'translate3d(0,0,0)'; let inner = el.getElementsByClassName('inner-hook')[0]; inner.style.webkitTransform = 'translate3d(0,0,0)'; inner.style.transform = 'translate3d(0,0,0)'; el.addEventListener('transitionend', done); }); }, afterDrop: function (el) { let ball = this.dropBalls.shift(); if (ball) { ball.show = false; el.style.display = 'none'; } } } }
文件位置:src/common/js/date.js
export function formatDate(date, fmt) { if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); } let o = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds() }; for (let k in o) { if (new RegExp(`(${k})`).test(fmt)) { let str = o[k] + ''; fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str)); } } return fmt; } function padLeftZero(str) { return ('00' + str).substr(str.length); } import {formatDate} from '../../common/js/date'; filters: { formatDate(time) { let date = new Date(time); return formatDate(date, 'yyyy-MM-dd hh:mm'); } } }
export default { mounted() { console.log('mounted'); this._initScroll(); this._initPics(); }, updated() { console.log('updated'); this._initScroll(); this._initPics(); } }
本地存儲相關操做封裝
文件位置:src/common/js/store.js
// 存儲到本地存儲 export function saveToLocal(id, key, value) { let seller = window.localStorage.__seller__; if (!seller) { seller = {}; seller[id] = {}; } else { seller = JSON.parse(seller); if (!seller[id]) { seller[id] = {}; } } seller[id][key] = value; window.localStorage.__seller__ = JSON.stringify(seller); } // 從本地存儲裏面讀取 export function loadFromLocal(id, key, def) { /* eslint-disable semi */ let seller = window.localStorage.__seller__; if (!seller) { return def; } seller = JSON.parse(seller)[id]; if (!seller) { return def; } let ret = seller[key]; return ret || def; }
解析url參數
文件位置: src/common/js/util.js
export function urlParse() { let url = window.location.search; let obj = {}; let reg = /[?&][^?&]+=[^?&]+/g; let arr = url.match(reg); if (arr) { arr.forEach((item) => { let tempArr = item.substring(1).split('='); let key = decodeURIComponent(tempArr[0]); let val = decodeURIComponent(tempArr[1]); obj[key] = val; }) } return obj; }
項目編譯打包
cnpm run build
配置打包規範:config/index.js
module.exports = { build: { productionSourceMap: true, port: 9000 }, dev: { } }
利用express編寫一個本地服務器
文件位置:./prod.server.js
let express = require('express'); let config = require('./config/index'); let port = process.env.PORT || config.build.port; let app = express(); let router = express.Router(); router.get('/', function (req, res, next) { req.url = '/index.html'; next(); }); app.use(router); let appData = require('./data.json'); let seller = appData.seller; let goods = appData.goods; let ratings = appData.ratings; let apiRoutes = express.Router(); apiRoutes.get('/seller', function (req, res) { res.json({ error: 0, data: seller }) }); apiRoutes.get('/goods', function (req, res) { res.json({ error: 0, data: goods }) }); apiRoutes.get('/ratings', function (req, res) { res.json({ error: 0, data: ratings }) }); app.use('/api', apiRoutes); app.use(express.static('./dist')); module.exports = app.listen(port, function (err) { if (err) { console.log(err); return; } console.log('Listening at http://localhost:' + port); });
Eslint規範整體設置
項目開發流程
需求分析,腳手架工具,數據mock,架構設計,代碼編寫,自測,編譯打包。
能夠看看別人的代碼
仿【餓了麼】訂餐軟件的一個demo
https://github.com/guxun12/ele_demo
參考資料&資源
慕課網視頻,Vue.js高仿餓了麼外賣App
Vue.js 高仿餓了麼外賣 App 課程源碼,課程地址: http://coding.imooc.com/class...
推薦閱讀 點擊標題可跳轉
【面試Vue全家桶】vue前端交互模式-es7的語法結構?async/await
【面試須要】掌握JavaScript中的this,call,apply的原理
2019年的每一天日更只爲等待她的出現,好好過餘生,慶餘年 | 掘金年度徵文
這是一個有質量,有態度的公衆號
點關注,有好運