vue饿了么

项目一、Vue外卖App

MVVM架构:

View ==== ViewModel ==== Model
视图 通讯 数据
DOM 观察者 Javascrip对象

View和model通过 ViewModel来通信,但数据发生变化,Viewmodel能够观察到这种数据的变化,然后通知到对应的视图做自动更新;当用户操作View视图,ViewModel也能监听到视图的变化,然后通知数据做改动,实现了数据的双向绑定。

应用场景:
针对具有复杂交互逻辑的前端应用;
它可以提供基础的架构抽象;
可以通过AJAX数据持久化,保证前端用户体验

好处:
当前端和数据做一些操作的时候,可以通过AJAX请求对后端做数据持久化,不需要刷新整个页面,只需要改动DOM里需要改动的那部分数据。特别是移动端应用场景,刷新页面太昂贵,会重新加载很多资源,虽然有些会被缓存,但是页面的DOM,JS,CSS都会被页面重新解析一遍,因此移动端页面通常会做出SPA单页应用。

Vue.js的特点: MVVM框架、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。

核心思想:数据驱动、组件化。
该项目只提取饿了么其中一个模块–商家模块进行开发

项目开发的一个完整流程:
项目的需求分析–脚手架工具–数据mock–架构设计–代码编写–自测–编译打包等方面完全简讲述开发一个web的全流程,更好地了解一个项目从0到1的过程。
当然这个项目在开发中也会以线上生产环境的代码质量来要求。

这个过程还包括:
代码开发及测试环节:以像素级完美还原UI设计图;以真实外卖APP数据做演示,以保证代码无兼容性问题。

代码规范:
项目中所用的代码大到架构设计、组件抽象、模块拆分、代码风格统一、JS变量命名规范、CSS代码规范。致于编写高可维护、易于拓展、通用性强的代码,了解真实互联网公司是如何开发前端项目的。

流程及开发方法:
项目完整的开发流程;组件化、模块化的开发模式;使用Vue-cli脚手架初始化Vue.js项目;webpack的打包原理;模拟json后端数据,前后端分离开发;es6+eslint的开发方式。

第三方组件:
使用stylus编写模块化的CSS;使用vue-router开发单页应用;使用vue-resource与后端数据交互;在Vue.js框架里和第三方JS插件交互。

设计思想与模式:
使用Vue.js的过渡编写酷炫的交互动画;移动端设备像素比;制作并使用图标字;解决移动端1px边框问题;移动端经典的css sticky footer布局;flex弹性布局。
vue组件化:

在vue.js中,每个组件都对应一个viewModel,最终生成一个viewModel的树

组件设计原则:
页面上每一个独立的可视/可交互区域都可以视为一个组件。比如一个页面的header、footer等等;
每个组件对应一个工程目录,组件所需要的各种资源在这个目录下就近维护。就近维护原则就体现了前端工程化思想,每个开发者都知道自己所开发的单元,代码存在对应的组件目录中。在vue.js中可以通过.vue文件来实现;
页面不过是组件的容器,组件可以嵌套自由组合形成完整地页面。在本次项目开发中,会拆分成一个个组件。

组件创建 :

1、创建一个新的Vue实例,并设置挂载点
2、利用template标签处理复杂组件
3、创建组件,导入导出组件,注册组件
4、通过props向组件中传递数据(处理父组件向子组件传递数据),props可以是数组,也可以是对象。
5、组件的通信:

例如 : App.vue为父组件,有子组件A.vue、B.vue

vue1.0:
APP通过props传递数据给A.B;
A.B通过$dispatch调用App的Event并传递数据给App;
A通过$dispatch调用App的Event并传递数据,App通过$broadcas调用B的Event并传递数据;
可以引入vuex。
vue2.0:
父传子:props
父访问子:ref 绑定在dom、$refs获取dom;
子向父传递:子组件$emit()触发,父组件$on()监听。

实战项目

技术栈: vue2+vue-router+vue-cli2+vue-resource+stylus+flex布局+es6+eslint+wepack2

Vue-cli
是Vue的脚手架工具[脚手架,搭建的架子-编写代码,帮助我们写好Vue.js基础代码的工具,能过实现:目录结构、本地调试、代码部署、热加载、单元测试]

项目开始
- node -v 检查node的版本【v7.7.4】要确认node的版本在4以上
- 安装vue-cli npm install -g vue-cli [-g全局安装]
- vue 查看vue命令 vue-list 列出可用的模板
- vue init webpack +template name【模板名(sell)】 初始化,安装webpack模板 ->Project name(sell)回车【默认工程名】 ->Project description

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

@media(-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5)
    .border-1px
        &::after
            -webkit-transform:scaleY(0.7)
            transform:scaleY(0.7)

@media(-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2)
    .border-1px
        &::after
            -webkit-transform:scaleY(0.5)
           transform:scaleY(0.5)
**sticky-footer布局** header组件的详情页采用sticky-footer布局,主要特点是如果内容不够长,页脚部分也会贴在视窗底部,内容足够长,就会将页脚推到内容底部,父级position:fixed,内容设为padding-bottom:64px,页脚相对定位,margin-top:-64px **自适应的布局** 1、左侧宽度固定,右侧宽度自适应
// 左侧固定width:80px,右侧自适应
parent:
    display:fiexd;
child-left:
    flex:0 0 80px
child-right:
    flex:1
2、元素宽度自适应设备宽度,且元素要求等宽高样式
// stylus语法
.img_header
    position:relative
    width:100% // width是 设备宽度
    height:0
    padding-top:100% // 高度设为0,使用padding撑开
    .img
        position:absolute //定位布局
        top:0
        left:0
        width:100%
        height:100%
**背景模糊效果:** `filter:blur(10px)`,注意,所有在内的子元素也会模糊,包括文字,所以采用定位布局,背景单独占用一个层,ios有一个设置`backdrop-filter:blur(10px)`,只会模糊背景,但不支持android **transition过渡** `` 在购买控件中使用transition过渡效果,实现添加减少按钮的动效,和小球飞入购物车的动效(模仿贝塞尔曲线的效果) vue2.x里面定义了transition过渡状态, name - string, 用于自动生成 CSS 过渡类名。
例如:name: 'fade' 将自动拓展为.fade-enter.fade-enter-active等。默认类名为 "v"
fade-enter   定义进入过渡的开始状态。在元素被插入时生效,在下一个帧移除
fade-enter-active  定义过渡的状态。在元素整个过渡过程中作用,在元素被插入时生效,在 transition/animation 完成之后移除。这个类可以被用来定义过渡的过程时间,延迟和曲线函数。
fade-leave  定义离开过渡的开始状态。在离开过渡被触发时生效,在下一个帧移除。
fade-leave-active  定义过渡的状态。在元素整个过渡过程中作用,在离开过渡被触发后立即生效,在 transition/animation 完成之后移除。这个类可以被用来定义过渡的过程时间,延迟和曲线函数。
包括transition过渡的钩子函数
// 进入中
before-enter
// 离开时
before-leave
before-appear
enter
leave
appear
after-enter
after-leave
after-appear滚动
enter-cancelled
leave-cancelled (v-show only)
appear-cancelled
**seller组件** 1、seller页面中商品商家实景图片横向滚动 每个li要`display:inline-block`,因为`width`不会自动撑开父级ul,所以需要计算ul的`width`,(每一张图片的`width+margin`)*图片数量-一个margin,因为最后一张图片没有`margin` 同时`new BScroll`里面要设置`scrollX: true,eventPassthrough: ‘vertical’`,// 滚动方向横向 2、打开seller页面,无法滚动 出现这种现象是因为better-scroll插件是严格基于DOM的,数据是采用异步传输的,页面刚打开,DOM并没有被渲染,所以,要确保DOM渲染了,才能使用better-scroll 方案:用到`mounted`钩子函数,同时搭配`this.$nextTick()`。 3、在seller页面,刷新后,无法滚动 出现这种情况是应为mounted函数在整个生命周期中只会只执行一次 方案:使用watch方法监控数据变化,并执行滚动函数`this._initScroll();this._initPicScroll();`。 **缓存数据** 使用`windons.localStorage`保存和设置缓存信息,封装在store.js文件内
// 将页面信息保存到 localStorage里
export function saveToLocal (id, key, value) {
  let store = window.localStorage._store_; // 新定义一个key值_store_,存放要保存的数据对象
  // _store_ {
  //   store[id]: {
  //     key: value
  //   }
  // }
  if (!store) {
    store = {};
    store[id] = {};
  } else {
    store = JSON.parse(store); // String格式--> json格式
    if (!store[id]) {
      store[id] = {};
    }
  }
  store[id][key] = value;
  window.localStorage._store_ = JSON.stringify(store); // 将json格式转成String格式,存放到window.localStorage._store中
}
//将localStorage信息设置到页面中
export function loadFromLocal(id, key, defaults) {
  let store = window.localStorage._store_;
  if (!store) { // 一开始是没有的,因为没有点击事件,所以显示默认数据
    return defaults;
  }
  store = JSON.parse(store)[id]; // 将json格式-->String格式
  // console.log(store); // {"isFavorite":true}
  if (!store) {
    return defaults;
  }
  let ret = store[key];
  return ret || defaults;
}
**解析url,得到商家信息,包括id,name,在获取数据时,直接赋值,商家的id或name会被丢掉** 使用`window.localStorage.search`获取url地址,并进行解析,封装在util.js文件内
/**
 * http://localhost:8080/#/Seller
 *  https://h5.ele.me/shop/#id=151667422
 * ?id=1234&name=zpxf
 */

/方法一:
export function urlParse() {
  let url = window.location.search;
  let obj = {};
  let reg = /[?&][^?&]+=[^?&]+/g;
  let arr = url.match(reg);
  // ['?id=12345', '&a=b']
  if (arr) {
    arr.forEach((item) => {
      let tempArr = item.substring(1).split('=');
      // 因为tempArr是url中的参数,所以要用decode进行转化
      let key = decodeURIComponent(tempArr[0]);
      let val = decodeURIComponent(tempArr[1]);
      obj[key] = val;
    });
  }
  return obj;
};

/方法二:
export function urlParse() {
  let urlArr = window.location.search.substr(1).split('&'); // 截取掉?,并以&分开,存入数组
  // console.log(urlArr); // ["id=1234", "name=zpxf"]
  let obj = {};
  if (urlArr) {
    urlArr.forEach((item) => {
      let arr = item.split('='); // 每一项用=分开存入数组,arr[0]=key,arr[1]=value
      // console.log(arr); // [id,1234] [name,zpxf]
      let key = decodeURIComponent(arr[0]); // 对url解码
      let val = decodeURIComponent(arr[1]);
      obj[key] = val;
    });
  }
  // console.log(obj); // {id: "1234", name: "zpxf"}
  return obj;
};
需要将得到的id和name带到数据中,实际上在获取数据的时候,并没有带着id和name,这时就要用到es6语法中`Object.assign()`,【可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象】
this.seller = Object.assign({}, this.seller, response.data);
// 即将vm.seller属性和请求返回数据对象合并到空对象,然后赋值给vm.seller,这里加上this.seller即提供了一种可扩展的机制,倘若原来的属性中有预定义的其他属性。
**good,ratings,seller组件之间切换时会重新渲染** 方案: 在app.vue内使用keep-alive,保留各组件状态,避免重新渲染
<keep-alive>
    <router-view :seller="seller"></router-view>
</keep-alive>
**项目总结** 1、vue-router 使用``组件完成导航,``默认会被渲染成一个 ` ` 标签,但必须使用`to`属性,指定连接
 <!-- 导航 -->
<router-link to="/home">home</router-link>
<router-link to="/about">about</router-link>

<!-- 路由出口 组件渲染容器 -->
<router-view></router-view>
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Resource);
// 定义每个路由对应一个组件
let router = new Router({ // 创建 router 实例,然后传 `routes` 配置
  linkActiveClass: 'active',
  routes: [{
      path: '/Header',
      name: 'Header',
      component: Header
    },
    {
      path: '/Seller',
      name: 'Seller',
      component: Seller
    },
    {
      path: '/Goods',
      name: 'Goods',
      component: Goods
    },
    {
      path: '/Ratings',
      name: 'Ratings',
      component: Ratings
    }
  ]
});
export default router;
router.push('goods');// 相当于页面初始化,显示goods的内容

// 挂载
new Vue({
  el: '#app',
  template: '<App/>',
  router: router,
  components: { App }
});

//或者另一种挂载
new Vue({
  template: '<App/>',
  router: router,
  components: { App }
}).$mount(#app);//手动挂载,#app
2、vue-resource 通过`this.$http.get`来定义通过vue实例来发送get请求,然后通过then后面的回调函数将请求成功的数据接收,通过状态码来判断是否成功以及复制给vue的数据对象。由于这里是用mock数据(模拟后台数据),所以用的模拟状态码。 同时,这里省略了`errorcallback`的定义,正常开发中需要进行定义,甚至可以利用vue-resource的`inteceptor`进行体验优化,比如定义请求时的loading动画界面。在vue中即可以提取出loading组件。
const ERR_OK = 0;//表示没有错误信息,即获取数据成功
this.$http.get('/api/seller').then((response) => {
  response = response.body;
  if (response.errno === ERR_OK) {
    this.seller = Object.assign({}, this.seller, response.data);
  }
});
3、Object.assign(traget,source1,source2) 这是es6的语法,用于对象合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
var target = { a: 1, b: 1 };

var source1 = { b: 2, c: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
另外需要注意的是Object.assign()方法只会拷贝对象自身的并且可枚举的属性到目标身上。也就意味着继承属性和不可枚举属性是不能拷贝的,而且拷贝是对象的属性的引用而不是对象本身。 4、组件间通讯 vue是组件式开发所以组件间通讯是必不可少的。vue提供了一种方式,即在子组件定义props来传递父组件的数据对象。
// 父组件
<v-header :seller="seller"></v-header>

// 子组件 header.vue
props: {
  seller: {
    type: Object
  }
}
如果是子组件想传递数据给父组件,需要派发自定义事件,使用`$emit`派发,父组件使用`v-on`接收监控(v-on可以简写成@)
// 子组件 RatingSelect.vue,派发自定义事件isContent,将this.onlyContent数据传给父级

this.$emit('isContent', this.onlyContent);
this.$emit('selRatings', this.selectType);

// 父组件 foodInfo.vue 在子组件的模板标签里,使用v-on监控isContent传过来的数据

<v-ratingselect :ratings="food.ratings" :select-type="selectType" :only-content="onlyContent" :desc="desc" @selRatings="filterRatings"@isContent="iscontent"></v-ratingselect>
非父子组件之间通信,vue官方锐减使用vuex,但是这里相较简单,所以采用的是利用给一个空实例eventHub,作为两个组件的中央数据总线,使用this. root.eventHub. emit来派发自定义事件,使用this. root.eventHub. on来监控 这里特别说明$root,官方解释:表示当前组建树的根实例,如果跟实例没有父实例,次实例将会是自己
//main.js
new Vue({
  // el: '#app',
  router,
  template: '<App/>',
  components: {
    App
  },
  data: {
    eventHub: new Vue() // 给data添加一个 名字为eventHub 的空vue实例,用来传输非父子组件的数据
  }
}).$mount('#app'); // 手动挂载,#app



//foodInfo.vue组件派发自定义事件cart.add,传递信息event.target
this.$root.eventHub.$emit('cart.add', event.target); // 传输点击的目标元素


//Shopcart.vue组件监控cart.add
created() {
    // 获取按钮组件的点击的元素,用在drop方法里
    this.$root.eventHub.$on('cart.add', this.drop);
},
methods:{
    drop(element){
        //to do ...
    }
}
5、组件提取管理

将相同样式或功能区块单独提出来,作为一个组件。
另外组件中用到图片等资源就近原则,即可以在组件文件中新建images文件夹。

抽离组件遵循原则: 要尽量遵循单一职责原则,复用性根更高,不要设置额外的margin等影响布局的东西 **css预处理器 stylus** 全局安装,安装之前你需要你安装node.js

$ npm install stylus -g
index.styl是stylus文件的入口文件,里面使用@import引入各种styl文件

@import './mixin.styl'
@import './base.styl'
@import './icon.styl'
在入口文件main.js中全局引用index.styl
import 'common/stylus/index.styl';
// 使用stylus可以快速且保证兼容的实现border-1px:
//mixin.styl

border-1px($color)
    position:relative
    &:after
        content:''
        display:block
        position:absolute
        left:0
        bottom:0
        width:100%
        border:1px solid $color
@media(-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5)
    .border-1px
        &::after
            -webkit-transform:scaleY(0.7)
            transform:scaleY(0.7)
@media(-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2)
    .border-1px
        &::after
            -webkit-transform:scaleY(0.5)
            transform:scaleY(0.5)
**打开app应用,默认显示goods内容** 想要达到这种目的,有两种方法,一种是利用重定向,另一种是利用vue-route的导航式编程。 1、重定向
//在router的index.js文件中设置,要多写一个对象,指向目标组件
routes: [
    {
      path: '/',
      redirect: '/Goods',// 重定向
      name: 'Goods',
      component: Goods
    },
    {
      path: '/Goods',
      name: 'Goods',
      component: Goods
    },
    {
      path: '/Header',
      name: 'Header',
      component: Header
    },
    {
      path: '/Seller',
      name: 'Seller',
      component: Seller
    },
    {
      path: '/Ratings',
      name: 'Ratings',
      component: Ratings
    }
  ]
2、导航式编程
router.push('/Goods');
**关于eslint** `eslint` 是一个js代码风格检查器,配合vue-cli脚手架中的热更新,可以很方便的定位和提示错误。在公司多人协作开发时可以确保代码风格保持一致,可以很方便的阅读他人的代码 **其他** 1、1.0升级到2.0 - 配置文件修改 package.json 文件修改 只需要把2.0的package.json拷贝到1.0即可 build 目录修改 只需要把2.0的拷贝build到1.0即可—>修改配置vue兼容配置,别名的配置
 vue:{
   loaders: utils.cssLoaders({sourceMap:useCssSourceMap}),
   postcss: [
     require('autoprefixer')({
         browsers: ['last 2 versions','Android >=4.0']
     })
   ]
 }
支持android浏览器加上前缀
alias: {
  'src': path.resolve(__dirname, '../src'),
  'common': path.resolve(__dirname, '../src/common'),
  'components': path.resolve(__dirname, '../src/components')
}
  • config 目录修改

  • src main.js

    • vue-router变化 初始化路由变化:不在是router.start而是创建实例,多了一个render方法router.map替换为一个在router实例里的一个routes选择数组。 v-link指令替换为<router-link>组件
    • vue2.0语法变化:
    • v-for指令的变化(item,index) in items还有track替换成key
    • v-elv-ref指令的变化他两个被废除替换成ref属性【后跟字符串,绑定dom对象,可以简化API】通过$refs调用;
    • 模板变化,组件只允许一个根元素,也就是tepmlate只能包含一个根元素;
    • 组件通信变化$dispatch[它是通过子组件向父组件去冒泡,把事件一层一层的冒出去而$emit则不是]废除,替换成$emit[它是在当前实例上派发一个事件,也就是只能在当前组件v-on【@】这个事件才能监听到而不是在父组件];
    • 事件监听变化,废除events属性,不能在子组件直接修改父组件传入的prop[可能会让父组件的状态不可控,所以不能,组件内修改 prop 是反模式 (不推荐的) 的。比如,先声明一个 prop ,然后在组件中通过 this.myProp = 'someOtherValue'改变 prop 的值。根据渲染机制,当父组件重新渲染时,子组件的内部 prop 值也将被覆盖]替代成:通过 data 属性,用 prop 去设置一个 data 属性的默认值,通过 computed 属性;
    • 过渡的变化,升级成transition组件,包在动画的外层; transition-group组件动画列表
    • 小球下落动画实现的变化 利用transition组,再利用钩子函数实现动画【在 enter 和 leave 中,回调函数 done 是必须的】。否则,它们会被同步调用,过渡会立即完成。
    • keep-alive属性变为<keep-alive>组件
      2、项目运行
克隆项目到本地
git clone 地址

安装依赖
npm install

本地开发,开启服务器,浏览器访问http://localhost:8080
npm run dev

构建生产
npm run build

运行打包文件
node prod.server.js 
会看到 Listening at http://localhost:9000 在浏览器中打开即可

3、手机测试网页技巧
将localhost换成自己的ip,Windows在命令行执行ipconfig查看,mac执行ifconfig查看。
然后复制地址栏地址,进入草料二维码,然后生成二维码,然后用手机扫一扫就可以查看了,前提是,你手机和电脑必须在同一个局域网。

vue小知识

  • vue中computed和methods都可以处理大量的逻辑diamante,computed成为计算属性,计算就要返回一个计算的结果,所以,当要处理大量的逻辑,最后要取得最后的结果的时候可以用computed;methods:是方法的意思,在js中,把一些函数叫做方法,一般情况下,要触发这个方法就要执行,要执行就要有一个源来触发,所以就需要一个事件源。这是computed的一点不同之处;
  • 对比computed和methods:computed计算的结果如果不发生改变就不会触发result这个函数。而methods中一般都是定义的需要事件触发的函数。每次只要触发事件,就会执行对应的方法。如过把computed的方法写到method中会;浪费性能。computed必须返回一个值页面绑定的才能取得值,而methods中可以只执行逻辑代码,可以返回值

资源
- vue.js 权威指南 https://item.jd.com/12028224.html
- vue.js官网 https://vuejs.org.cn
- vue-cli https://github.com/vue.js/vue-cli
- vue-resource https://github.com/vuejs/vue-resource
- vue-router https://github.com/vuejs/vue-router
- better-scroll https://github.com/ustbhuangyi/better-scroll
- webpack官网 http://webpack.github.io
- stylus 中文文档 http://www,zhangxinxu.com/jq/stylus/
- es6入门学习 http://es6.ruanyifeng.com/
- eslintg规则 http://eslint.org/docs/rules/
- 设备像素比 http://www.zhangxinxu.com/wordpress/2012/08/window-decvicepixelratio/
- fle布局 http://www.ruanyiifeng.com/blog.2015/07/fles-grammar.html?utm_source=tuicool
- 贝塞尔曲线测试 http://cubic-bazier.com/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值