Vue.js高仿饿了么外卖App学习记录_数据

开发一款vue.js开发一款app,使用vue.js是一款高效的mvvm框架,它轻量,高效,组件化,数据驱动等功能便于开发。使用vue.js开发移动端app,学会使用组件化,模块化的开发方式。

学习了如何根据需求分析开发,使用脚手架工具,数据mock,架构设计,自己测试,编译打包等流程。

线上生产环境,如何考虑架构设计,组件抽象,模块拆分,代码风格统一,变量命名要求规范等优点。

一款外卖app,商家页面,商家基本信息(顶部),商品区块,商品列表,分类列表,小球飞入购物车的动画。商品详情页,需要有顶部商品的大图,商品的详细信息,以及还有商品的评价列表。

商品,评论列表,商家展示商家的详情信息。

用vue-resource与后端做数据交互,vue-router前端路由,better-scroll的Js库等。使用vue-cli脚手架,搭建基本代码框架,vue-router官方插件管理路由。vue-resource是用于ajax通信的,webpack构建工具的使用。

Vue是一套用于构建用户界面的渐进式JavaScript框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,方便与第三方库或既有项目整合。

Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,Vue.js 自身不是一个全能框架——它只聚焦于视图层。因此它非常容易学习,非常容易与其它库或已有项目整合。

目录/文件

说明

build

项目构建(webpack)相关代码

config

配置目录,包括端口号等。我们初学可以使用默认的。

node_modules

npm 加载的项目依赖模块

src


包含了几个目录及文件:

  • assets: 放置一些图片,如logo等。
  • components: 目录里面放了一个组件文件,可以不用。
  • App.vue: 项目入口文件,我们也可以直接将组件写这里,而不使用 components 目录。
  • main.js: 项目的核心文件。

          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.js高仿饿了么外卖App学习记录_数据_02

          数据响应原理

          组件化原理

          Vue.js高仿饿了么外卖App学习记录_3d_03

          vue-cli,vue.js的开发利器,脚手架

          vue-cli可以搞定,目录结构,本地调试,代码部署,热加载,单元测试。

          vue-cli的安装方法:

          Vue.js高仿饿了么外卖App学习记录_ico_04

          node -v

          mac 

          sudo npm install -g vue-cli

          Vue.js高仿饿了么外卖App学习记录_json_05

          Vue.js高仿饿了么外卖App学习记录_css_06

          使用webpack模板,名字sell,外卖app。

          Vue.js高仿饿了么外卖App学习记录_css_07

          Vue.js高仿饿了么外卖App学习记录_3d_08

          Vue.js高仿饿了么外卖App学习记录_ico_09

          Vue.js高仿饿了么外卖App学习记录_css_10

          Vue.js高仿饿了么外卖App学习记录_数据_11

          Vue.js高仿饿了么外卖App学习记录_3d_12

          Vue.js高仿饿了么外卖App学习记录_ico_13

          运行效果:

          Vue.js高仿饿了么外卖App学习记录_数据_14

          然后把项目放进你的编辑器

          mode_modules文件夹:npm install 安装的依赖代码库

          src文件夹是我们存放的源码

          Vue.js高仿饿了么外卖App学习记录_css_15

          这个文件跟我不一样也没事。

          Vue.js高仿饿了么外卖App学习记录_数据_16

          editorconfig是编辑器的配置

          eslintignore为忽略语法检查的目录文件

          Vue.js高仿饿了么外卖App学习记录_css_17

          eslintrc.js为eslint的配置文件

          Vue.js高仿饿了么外卖App学习记录_css_18

          商品页面:

          Vue.js高仿饿了么外卖App学习记录_css_19

          商品页_公共以及优惠信息

          Vue.js高仿饿了么外卖App学习记录_json_20

          商品页购物车详情

          Vue.js高仿饿了么外卖App学习记录_数据_21

          商品页面_商品详情页面

          Vue.js高仿饿了么外卖App学习记录_数据_22

          评价页

          Vue.js高仿饿了么外卖App学习记录_数据_23

          商家页

          Vue.js高仿饿了么外卖App学习记录_数据_24

          设备像素比devicePixelRatio

          在移动端,devicePixelRatio指的是window.devicePixelRatio。

          移动端设备分为非视网膜屏幕和视网膜屏幕。

          window.devicePixelRatio是设备上物理像素和设备独立像素的比例,公式表就是:window.devicePixeRatio = 物理像素/dips。

          icomoon.io,图标字体制作

          mock数据,模拟后台数据

          Vue.js高仿饿了么外卖App学习记录_css_25

          icon- 开头的图标(如图所示) 

          Vue.js高仿饿了么外卖App学习记录_3d_26

          首先进入网页https://icomoon.io/ 

          然后点击右上角的“IcoMoon APP”按钮,选择导入自己的SVG图来生成ico-的图标,点击新页面左上角的“Inport ICONS”。 

          Vue.js高仿饿了么外卖App学习记录_json_27

          Vue.js高仿饿了么外卖App学习记录_3d_28

          在devServer下面加入

          Vue.js高仿饿了么外卖App学习记录_ico_29

          页面骨架开发

          sell->build->confi->node_modules->resource, img, psd, svg ->src, common->components, app.vue->static

          Vue.js高仿饿了么外卖App学习记录_数据_30

          <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上,横竖屏不分,默认都为竖屏的宽度,兼容的最好写法。

          Vue.js高仿饿了么外卖App学习记录_3d_31

          什么是viewport,它是用户网页的可视区域,翻译就是视区。

          手机浏览器是把页面放在一个虚拟的"窗口"(viewport)中,通常这个虚拟的"窗口"(viewport)比屏幕宽,这样就不用把每个网页挤到很小的窗口中(这样会破坏没有针对手机浏览器优化的网页的布局),用户可以通过平移和缩放来看网页的不同部分。

          Vue.js高仿饿了么外卖App学习记录_数据_32

          没有添加viewport的效果:

          Vue.js高仿饿了么外卖App学习记录_数据_33

          加了viewport的效果:

          Vue.js高仿饿了么外卖App学习记录_css_34

          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个物理像素

          Vue.js高仿饿了么外卖App学习记录_数据_35

          viewport是浏览器窗口,代表浏览器的可视区域,就是浏览器中用来显示网页的部分区域。

          像素单位有设备像素,逻辑像素,css像素。

          设备像素也叫物理像素。

          什么是设备像素,它指的是显示器上的真实像素,每个像素的大小是屏幕固有的属性。

          设备分辨率是用来描述这个显示器的宽和高分别有多少个设备像素。

          设备像素和设备分辨率由操作系统来管理。

          Vue.js高仿饿了么外卖App学习记录_ico_36

          全局安装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数据接口

          Vue.js高仿饿了么外卖App学习记录_数据_37

          // 文件位置: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>


          Vue.js高仿饿了么外卖App学习记录_ico_38

          Vue.js高仿饿了么外卖App学习记录_css_39

          安装ajax异步请求插件vue-resource

          cnpm install vue-resource --save-dev


          文件位置:src/APP.vue

          Vue.js高仿饿了么外卖App学习记录_json_40

          <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

          Vue.js高仿饿了么外卖App学习记录_数据_41

          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)
          });


          Vue.js高仿饿了么外卖App学习记录_ico_42

          安装better-scroll

          cnpm install better-scroll --save-dev


          Vue.js高仿饿了么外卖App学习记录_ico_43

          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 => {
          });
          }
          }


          Vue.js高仿饿了么外卖App学习记录_css_44

          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.js高仿饿了么外卖App学习记录_css_45

          Vue.set(this.food, 'count', 1);


          小球动画函数监听

          Vue.js高仿饿了么外卖App学习记录_数据_46

          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

          Vue.js高仿饿了么外卖App学习记录_ico_47

          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');
          }
          }
          }


          Vue.js高仿饿了么外卖App学习记录_ico_48

          export default {
          mounted() {
          console.log('mounted');
          this._initScroll();
          this._initPics();
          },
          updated() {
          console.log('updated');
          this._initScroll();
          this._initPics();
          }
          }


          本地存储相关操作封装

          文件位置:src/common/js/store.js

          Vue.js高仿饿了么外卖App学习记录_数据_49

          // 存储到本地存储
          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

          Vue.js高仿饿了么外卖App学习记录_ico_50

          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

          Vue.js高仿饿了么外卖App学习记录_css_51

          module.exports = {
          build: {
          productionSourceMap: true,
          port: 9000
          },
          dev: {


          }
          }


          利用express编写一个本地服务器

          文件位置:./prod.server.js

          Vue.js高仿饿了么外卖App学习记录_json_52

          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规范总体设置

          Vue.js高仿饿了么外卖App学习记录_css_53

          Vue.js高仿饿了么外卖App学习记录_数据_54

          Vue.js高仿饿了么外卖App学习记录_3d_55

          项目开发流程

          需求分析,脚手架工具,数据mock,架构设计,代码编写,自测,编译打包。

          Vue.js高仿饿了么外卖App学习记录_json_56