vue2.x饿了吗实战总结

vue2.x仿饿了么app项目总结

转自 https://blog.csdn.net/qq_39894133/article/details/78826450

仿饿了么app是基于vue2.x最新实战项目,用到的技术栈

vue2 + vue-router2 + vue-cli2+ axios + stylus + webpack2 + node.js

页面相对简单,所以没有用到vuex, 它更适合对复杂的单页面进行状态管理

实现功能:

 
  1. • Goods、Ratings、Seller组件视图均可上下滚动

  2. • 商品页 点击左侧menu,右侧list对应跳转到相应位置

  3. • 点击list查看商品详情页,父子组件的通信

  4. • 评论内容够可以筛选查看

  5. • 购物车组件,包括添加删除商品及动效,购物控件与购物车组件之间非父子组件通信,点击购物车图标,展示选择的商品列表

  6. • 商家实景图片可以左右滑动

  7. • loaclStorage缓存商家信息(id、name)

项目地址:https://github.com/moxiaojing...

如果觉得对您有帮助,您可以在右上角给我个star支持一下,谢谢!

1-项目结构分析:

 
  1. cmmon/---- 文件夹存放的是通用的css和fonts

  2. components/----文件夹用来存放我们的 Vue 组件

  3. router/----文件夹存放的是vue-router相关配置(linkActiveClass,routes注册组件路由)

  4. build/----文件是 webpack 的打包编译配置文件

  5. config/----文件夹存放的是一些配置项,比如我们服务器访问的端口配置等

  6. dist/----该文件夹一开始是不存在,在我们的项目经过 build 之后才会产出

  7. prod.server.js----该文件是测试是模拟的服务器配置,用来运行dist里面的文件,在config/index.js中,build对象中添加一条端口设置port:9000,

  8. App.vue----根组件,所有的子组件都将在这里被引用,eventHub空实例是用来组件间通信的中央数据总线作用,主要连接购买控件和购物车组件之间的数据通信

  9. index.html----整个项目的入口文件,将会引用我们的根组件 App.vue

  10. main.js----入口文件的 js 逻辑,在 webpack 打包之后将被注入到 index.html 中

2-各组件之间的关系:

 
  1. ├──src

  2. │ ├──Header.vue--头部组件

  3. │ │ ├──iconClassMap--图标组件(减,折,特,票,保)

  4. │ │ ├──Star.vue--星星评分组件

  5. │ ├──Goods.vue--商品组件

  6. │ │ ├──iconClassMap--图标组件(减,折,特,票,保)

  7. │ │ ├──Shopcart.vue--购物车组件,包括小球飞入购物车动画,使用this.\$root.eventHub.\$on('cart.add', this.drop)接收,并给drop方法使用

  8. │ │ ├──CartControl.vue--购买控件--选中数量返回给父组件goods,goods响应后,重新计算选中数量,并用this.\$root.eventHub.\$emit('name',event.target)将数据发送给购物车组件,

  9. │ │ ├──Foodinfo.vue--商品详情页

  10. │ │ │ ├──RatingSelect.vue--评价内容筛选组件

  11. │ ├──Ratings.vue--评论组件

  12. │ │ ├──RatingSelect.vue--评价内容筛选组件

  13. │ ├──Seller.vue--商家组件

  14. │ │ ├──iconClassMap--图标组件(减,折,特,票,保)

  15.  
  16. 独立组件

  17. ├──iconClassMap--图标组件(减,折,特,票,保)

  18. ├──split.vue--关于分割线组件

  19. ├──RatingSelect.vue--评价内容筛选组件

3-开发过程问题汇总:

3-1、better-scroll插件在移动端使用时需要设置click:true,否则移动端滑动无效

3-2、分开设置css样式:

  • 图标icon.css--文字图标样式,通过icommon.io网站 将svg图片转成文字图标样式

  • 公共base.css--处理设备像素比的一些样式,针对border-1px问题,不同设备像素比,显示的线条粗细不同

  • 工具mixin.css--设置border-1px样式和背景样式

3-2-1、这里着重解释一下border-1px的实现

当样式像素一定时,因手机有320px,640px等.各自的缩放比差异,所以设备显示像素就会有1Npx,2Npx.为保设计稿还原度,解决就是用media + scale.

  • 公式:设备上像素 = 样式像素 * 设备像素比

 
  1. 屏幕宽度: 320px 480px 640px

  2. 设备像素比: 1 1.5 2

  3.  
  4. 通过查询它的设备像素比 devicePixelRatio

  5.  
  6. 在设备像素比为1.5倍时, round(1px 1.5 / 0.7) = 1px

  7. 在设备像素比为2倍时, round(1px 2 / 0.5) = 1px

 
  1. // stylus语法

  2. border-1px($color)

  3. position:relative

  4. &:after

  5. content:''

  6. display:block

  7. position:absolute

  8. left:0

  9. bottom:0

  10. width:100%

  11. border:1px solid $color

  12.  
  13. @media(-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5)

  14. .border-1px

  15. &::after

  16. -webkit-transform:scaleY(0.7)

  17. transform:scaleY(0.7)

  18.  
  19. @media(-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2)

  20. .border-1px

  21. &::after

  22. -webkit-transform:scaleY(0.5)

  23. transform:scaleY(0.5)

更多了解设备像素比devicePixelRatiohttp://www.zhangxinxu.com/wor...

3-3、sticky-footer布局

主要特点是如果内容不够长,页脚部分也会贴在视窗底部,内容足够长,就会将页脚推到内容底部,父级position:fixed,min-height:100%;内容设为padding-bottom:64px,顶部,margin-top:-64px

3-4、要求自适应的布局

3-4-1、左侧宽度固定,右侧宽度自适应

 
  1. // 左侧固定width:80px,右侧自适应

  2. parent:

  3. display:fiexd;

  4. child-left:

  5. flex:0 0 80px

  6. child-right:

  7. flex:1

3-4-2、元素宽度自适应设备宽度,且元素要求等宽高样式

例如:商品详情页面的商品图片展示样式

 
  1. // stylus语法

  2. .img_header

  3. position:relative

  4. width:100% // width是 设备宽度

  5. height:0

  6. padding-top:100% // 高度设为0,使用padding撑开

  7. .img

  8. position:absolute //定位布局

  9. top:0

  10. left:0

  11. width:100%

  12. height:100%

3-5、背景模糊效果

filter:blur(10px),注意,所有在内的子元素也会模糊,包括文字,所以采用定位布局,背景单独占用一个层,ios有一个设置backdrop-filter:blur(10px),只会模糊背景,但不支持android

3-6、transition过渡

在购买控件中使用transition过渡效果,实现添加减少按钮的动效,和小球飞入购物车的动效(模仿贝塞尔曲线的效果)

vue2.x里面定义了transition过渡状态,
name - string, 用于自动生成 CSS 过渡类名。

 
  1. 例如:name: 'fade' 将自动拓展为.fade-enter,.fade-enter-active等。默认类名为 "v"

  2.  
  3. fade-enter

  4. fade-enter-active

  5. fade-leave

  6. fade-leave-active

包括transition过渡的钩子函数

 
  1. before-enter

  2. before-leave

  3. before-appear

  4. enter

  5. leave

  6. appear

  7. after-enter

  8. after-leave

  9. after-appear

  10. enter-cancelled

  11. leave-cancelled (v-show only)

  12. appear-cancelled

详情请查看vue2.x-transition详解

3-7、seller组件:

3-7-1问题一:seller页面中商品商家实景图片横向滚动

解决方案:每个li要display:inline-block,因为width不会自动撑开父级ul,所以需要计算ul的width,(每一张图片的width+margin)*图片数量-一个margin,因为最后一张图片没有margin
同时new BScroll里面要设置scrollX: true,eventPassthrough: 'vertical',// 滚动方向横向

3-7-2问题二:打开seller页面,无法滚动

问题分析:出现这种现象是因为better-scroll插件是严格基于DOM的,数据是采用异步传输的,页面刚打开,DOM并没有被渲染,所以,要确保DOM渲染了,才能使用better-scroll,
解决方案:用到mounted钩子函数,同时搭配this.$nextTick()

3-7-3问题三:在seller页面,刷新后,无法滚动

问题分析:出现这种情况是因为mounted函数在整个生命周期中只会只行一次
解决方案:使用watch方法监控数据变化,并执行滚动函数 this._initScroll();this._initPicScroll();

3-8、缓存数据

使用window.localStorage保存和设置缓存信息,封装在store.js文件内

 
  1. //将页面信息保存到localStorage里

  2. export function saveToLocal(id, key, value) {

  3. let store = window.localStorage._store_; // 新定义一个key值_store_,存放要保存的数据对象

  4. // _store_ {

  5. // store[id]: {

  6. // key: value

  7. // }

  8. // }

  9. if (!store) {

  10. store = {};

  11. store[id] = {};

  12. } else {

  13. store = JSON.parse(store); // String格式--> json格式

  14. if (!store[id]) {

  15. store[id] = {};

  16. }

  17. }

  18. store[id][key] = value;

  19. window.localStorage._store_ = JSON.stringify(store); // 将json格式转成String格式,存放到window.localStorage._store中

  20. }

  21. //将localStorage信息设置到页面中

  22. export function loadFromLocal(id, key, defaults) {

  23. let store = window.localStorage._store_;

  24. if (!store) { // 一开始是没有的,因为没有点击事件,所以显示默认数据

  25. return defaults;

  26. }

  27. store = JSON.parse(store)[id]; // 将json格式-->String格式

  28. // console.log(store); // {"isFavorite":true}

  29. if (!store) {

  30. return defaults;

  31. }

  32. let ret = store[key];

  33. return ret || defaults;

  34. }

  35.  

3-9、解析url,得到商家信息,包括id,name,在获取数据时,直接赋值,商家的id或name会被丢掉

使用window.localStorage.search获取url地址,并进行解析 
封装在tools.js文件内

 
  1. /**

  2. * http://localhost:8080/#/Seller

  3. * https://h5.ele.me/shop/#id=151667422

  4. * ?id=1234&name=zpxf

  5. */

  6.  
  7. /////////方法一:

  8. export function urlParse() {

  9. let url = window.location.search;

  10. let obj = {};

  11. let reg = /[?&][^?&]+=[^?&]+/g;

  12. let arr = url.match(reg);

  13. // ['?id=12345', '&a=b']

  14. if (arr) {

  15. arr.forEach((item) => {

  16. let tempArr = item.substring(1).split('=');

  17. // 因为tempArr是url中的参数,所以要用decode进行转化

  18. let key = decodeURIComponent(tempArr[0]);

  19. let val = decodeURIComponent(tempArr[1]);

  20. obj[key] = val;

  21. });

  22. }

  23. return obj;

  24. };

  25.  
  26. /////////方法二:

  27. export function urlParse() {

  28. let urlArr = window.location.search.substr(1).split('&'); // 截取掉?,并以&分开,存入数组

  29. // console.log(urlArr); // ["id=1234", "name=zpxf"]

  30. let obj = {};

  31. if (urlArr) {

  32. urlArr.forEach((item) => {

  33. let arr = item.split('='); // 每一项用=分开存入数组,arr[0]=key,arr[1]=value

  34. // console.log(arr); // [id,1234] [name,zpxf]

  35. let key = decodeURIComponent(arr[0]); // 对url解码

  36. let val = decodeURIComponent(arr[1]);

  37. obj[key] = val;

  38. });

  39. }

  40. // console.log(obj); // {id: "1234", name: "zpxf"}

  41. return obj;

  42. };

  43.  

我们需要将得到的id和name带到数据中,实际上在获取数据的时候,并没有带着id和name,这时就要用到es6语法中Object.assign(),官方解释为:可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

 
  1. this.seller = Object.assign({}, this.seller, response.data);

  2.  
  3. //即将vm.seller属性和请求返回数据对象合并到空对象,然后赋值给vm.seller,这里加上this.seller即提供了一种可扩展的机制,倘若原来的属性中有预定义的其他属性。

3-10、goods,ratings,seller组件之间切换时会重新渲染

解决方案:在app.vue内使用keep-alive,保留各组件状态,避免重新渲染

 
  1. <keep-alive>

  2. <router-view :seller="seller"></router-view>

  3. </keep-alive>

4-项目总结

4-1、vue-router

使用<router-link>组件完成导航,<router-link>默认会被渲染成一个 <a> 标签,但必须使用to属性,指定连接

 
  1. <!-- 导航 -->

  2. <router-link to="/home">home</router-link>

  3. <router-link to="/about">about</router-link>

  4.  
  5. <!-- 路由出口 组件渲染容器 -->

  6. <router-view></router-view>

 
  1. import Vue from 'vue';

  2. import Router from 'vue-router';

  3. Vue.use(Resource);

  4. // 定义每个路由对应一个组件

  5. let router = new Router({ // 创建 router 实例,然后传 `routes` 配置

  6. linkActiveClass: 'active',

  7. routes: [{

  8. path: '/Header',

  9. name: 'Header',

  10. component: Header

  11. },

  12. {

  13. path: '/Seller',

  14. name: 'Seller',

  15. component: Seller

  16. },

  17. {

  18. path: '/Goods',

  19. name: 'Goods',

  20. component: Goods

  21. },

  22. {

  23. path: '/Ratings',

  24. name: 'Ratings',

  25. component: Ratings

  26. }

  27. ]

  28. });

  29. export default router;

  30. router.push('goods');// 相当于页面初始化,显示goods的内容

  31.  
  32. // 挂载

  33. new Vue({

  34. el: '#app',

  35. template: '<App/>',

  36. router: router,

  37. components: { App }

  38. });

  39.  
  40. //或者另一种挂载

  41. new Vue({

  42. template: '<App/>',

  43. router: router,

  44. components: { App }

  45. }).$mount(#app);//手动挂载,#app

4-2、vue-resource





 

4-3、Object.assign(target, source1, source2);

这是es6的语法,用于对象合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。

注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

 
  1. var target = { a: 1, b: 1 };

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

  4. var source2 = { c: 3 };

  5.  
  6. Object.assign(target, source1, source2);

  7. target // {a:1, b:2, c:3}

  8.  

另外需要注意的是Object.assign()方法只会拷贝源对象自身的并且可枚举的属性到目标身上。也就意味着继承属性和不可枚举属性是不能拷贝的,而且拷贝是对象的属性的引用而不是对象本身。

4-4、组件间通讯

vue是组件式开发,所以组件间通讯是必不可少的。vue提供了一种方式,即在子组件定义props来传递父组件的数据对象。

 
  1. // 父组件

  2. <v-header :seller="seller"></v-header>

  3.  
  4. // 子组件 header.vue

  5. props: {

  6. seller: {

  7. type: Object

  8. }

  9. }

如果是子组件想传递数据给父组件,需要派发自定义事件,使用$emit派发,
父组件使用v-on接收监控(v-on可以简写成@)

 
  1.  
  2. // 子组件 RatingSelect.vue,派发自定义事件isContent,将this.onlyContent数据传给父级

  3.  
  4. this.$emit('isContent', this.onlyContent);

  5. this.$emit('selRatings', this.selectType);

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

  8.  
  9. <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,官方解释:表示当前组建树的根实例,如果根实例没有父实例,次实例将会是自己

 
  1. //main.js

  2. new Vue({

  3. // el: '#app',

  4. router,

  5. template: '<App/>',

  6. components: {

  7. App

  8. },

  9. data: {

  10. eventHub: new Vue() // 给data添加一个 名字为eventHub 的空vue实例,用来传输非父子组件的数据

  11. }

  12. }).$mount('#app'); // 手动挂载,#app

  13.  
  14.  
  15.  
  16. //foodInfo.vue组件派发自定义事件cart.add,传递信息event.target

  17. this.$root.eventHub.$emit('cart.add', event.target); // 传输点击的目标元素

  18.  
  19.  
  20. //Shopcart.vue组件监控cart.add

  21. created() {

  22. // 获取按钮组件的点击的元素,用在drop方法里

  23. this.$root.eventHub.$on('cart.add', this.drop);

  24. },

  25. methods:{

  26. drop(element){

  27. //to do ...

  28. }

  29. }

  30.  

3、父组件向子组件传递参数在子组件中通过props接收在父组件中通过:seller 通过api获取并且在data中定义

<router-view:seller="seller">content</router-view>子组件:

props:{ //这里router 路由的时候将这个传过来了所以可以直接使用 seller:{ type:Object } }

4、子组件向父组件传递参数 通过$emit一般是定义一个show或者是click事件然后通过$emit传递

eg:子级:

	togglecontent(event){
 	 if(!event._constructed){
   	 return;//设置他的原生事件不触发
  	}
  	this.onlycontent = !this.onlycontent;
  	this.$emit("toggleContent",this.onlycontent);
	}

//这里toggleContent表示在父级中调用的参数的调用名

父级:

a、先导入组件

	import rating from '../rating/rating.vue';

b、然后注册

	components:{
 	 rating
	}

c、在页面引用这个组件

	<rating :selectedType="selectedType"  v-on:ratingtypeSelect="ratingtypeselect" v-on:toggleContent="togglecontent"  :onlyContent="onlyContent" :destype="destype" :rating="food.ratings"></rating>

通过@toggleContent='togglecontent'或者v-on:toggleContent='togglecontent'

d、通过togglecontent(){}这个方法将子级定义出来的togglecontent值赋值给父级

	togglecontent(onlyContent){
  		this.onlyContent = onlyContent;
  		this.$nextTick(() =>{
    		this.scroll.refresh();
  		})
	},

4-5、组件提取管理

将相同样式或功能的区块单独提出来,作为一个组件。

另外组件中用到的图片等资源就近维护,即可以考虑在组件文件夹中新建images文件夹。

抽离组件遵循原则:
要尽量遵循单一职责原则,复用性更高,不要设置额外的margin等影响布局的东西

5-css预处理器--stylus

全局安装,安装之前你需要你安装 nodejs

$ npm install stylus -g

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

 
  1. @import './mixin.styl'

  2. @import './base.styl'

  3. @import './icon.styl'

在入口文件main.js中全局引用index.styl

import 'common/stylus/index.styl';
 
  1. // 使用stylus可以快速且保证兼容的实现border-1px:

  2. //mixin.styl

  3.  
  4. border-1px($color)

  5. position:relative

  6. &:after

  7. content:''

  8. display:block

  9. position:absolute

  10. left:0

  11. bottom:0

  12. width:100%

  13. border:1px solid $color

  14. @media(-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5)

  15. .border-1px

  16. &::after

  17. -webkit-transform:scaleY(0.7)

  18. transform:scaleY(0.7)

  19. @media(-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2)

  20. .border-1px

  21. &::after

  22. -webkit-transform:scaleY(0.5)

  23. transform:scaleY(0.5)

6-打开app应用,默认显示goods内容

想要达到这种目的,有两种方法,一种是利用重定向,另一种是利用vue-router的导航式编程。

6-1、重定向

 
  1. //在router的index.js文件中设置,要多写一个对象,指向目标组件

  2. routes: [

  3. {

  4. path: '/',

  5. redirect: '/Goods',// 重定向

  6. name: 'Goods',

  7. component: Goods

  8. },

  9. {

  10. path: '/Goods',

  11. name: 'Goods',

  12. component: Goods

  13. },

  14. {

  15. path: '/Header',

  16. name: 'Header',

  17. component: Header

  18. },

  19. {

  20. path: '/Seller',

  21. name: 'Seller',

  22. component: Seller

  23. },

  24. {

  25. path: '/Ratings',

  26. name: 'Ratings',

  27. component: Ratings

  28. }

  29. ]

6-2、导航式编程

router.push('/Goods');

7-关于eslint

eslint 是一个js代码风格检查器,配合vue-cli脚手架中的热更新,可以很方便的定位和提示错误。在公司多人协作开发时可以确保代码风格保持一致,可以很方便的阅读他人的代码。

刚使用时,会不太习惯,但是坚持下来,自己写的代码越来越整齐规范,越来越漂亮,自己会有很大的满足感。对自己,对他人都是一件非常有益的事!

8-关于其他

8-1、vue2相较vue1有很多地方改动

比如

  • v-for的书写格式,多出:key值,而且必须写

  • transition书写格式不在是在元素标签上写,而是作为一个标签<transition></transition>将目标元素包起来,过渡状态变为4种状态

  • v-el ,v-ref 最后都转化为了 ref ref="xxx"eg:<divclass='parent'ref='showhello'>hello</div> js中 this.$refs.showhello.innerText()

    a、ref在官网上的解释简单来说就是用来绑定某个dom元素,或者来说用来绑定某个组件,然后在$refs里面调用,

    b、如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素; 如果用在子组件上,引用就指向组件实例

    c、$refs作为子组件索引,ref本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!$refs 也不是响应式的,因此你不应该试图用它在模版中做数据绑定

具体详细内容,请参看官网从vue1.x迁移

8-2、项目运行

 
  1. 克隆项目到本地

  2. git clone https://github.com/JerryYgh/m-eleme.git

  3.  
  4. 安装依赖

  5. npm install

  6.  
  7. 本地开发,开启服务器,浏览器访问http://localhost:8080

  8. npm run dev

  9.  
  10. 构建生产

  11. npm run build

  12.  
  13. 运行打包文件

  14. node prod.server.js

  15.  
  16. 会看到 Listening at http://localhost:9000 在浏览器中打开即可

8-2浏览器兼容问题解决

 

 

postcss插件用来解决浏览器的兼容问题

 

8-4

9-学习参考

猜你喜欢

转载自blog.csdn.net/qq_42375089/article/details/81584289