【vue】饿了么项目-goods商品列表页开发
1.flex 属性是 flex-grow、flex-shrink 和 flex-basis 属性的简写属性。
flex-grow | 一个数字,规定项目将相对于其他灵活的项目进行扩展的量。 |
flex-shrink | 一个数字,规定项目将相对于其他灵活的项目进行收缩的量。 |
flex-basis | 项目的长度。合法值:"auto"、"inherit" 或一个后跟 "%"、"px"、"em" 或任何其他长度单位的数字。 |
2.采用绝对定位,相对于父元素
.good
display flex
position absolute
width 100%
top 174px
bottom 46px
overflow hidden
3.使用vue-resourse获取json并应用到模板
现在越来越多的数据传输方式都是json数据格式,包括用jquery开发时,也有很好用的$.ajax来进行数据请求与处理,那么vue-resource提供了一种类似的,并且api更加简洁易用,压缩后文件更小。配合ES 6的Lambda写法,更加优雅
官网:https://github.com/pagekit/vue-resource/blob/master/docs/http.md
props: {
seller: {
type: Object
}
},
data () {
return {
goods: [], //一开始goods为空
listHeight: [],
scrolly: 0,
selectedFood: {}
};
},
created() { //当这个组件被调用的时候,通过后端获得数据赋值给goods
this.$http.get('/api/goods').then((response) => { // '/api/goods'请求的是data.json下的goods数组
response = response.body;
if (response.errno === ERR_OK) {
this.goods = response.data;
this.$nextTick(() => { //可以用$nextTick
來确保Dom变化后
再执行一些事情
this._initScroll();
this._calculateHeight();
});
}
});this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
},
注:vue更新到2.0之后,作者就宣告不再对vue-resource更新,而是推荐的axios,它的基本用法可以参考:http://www.kancloud.cn/yunye/axios/234845
4.遍历取数据
<span class="text">
<span v-show="item.type>0" class=" icon" :class="classMap[item.type]"></span>{{item.name}}
</span>
classMap[item.type]是一个数组,通过item.type去取对应的class,item.type是data.json中mock的数据
5.display table
此元素会作为块级表格来显示(类似 <table>),表格前后带有换行符。
在table中可用vertical-align middle实现垂直居中
6.添加better-scroll依赖
链接:https://github.com/ustbhuangyi/better-scroll
<div class="menu-wrapper" ref="menuWrapper">
<ul>
<li v-for="(item, index) in goods" class="menu-item border-1px" :class="{'current':currentIndex === index}"
@click="selectMenu(index, $event)">
<span class="text">
<span v-show="item.type>0" class=" icon" :class="classMap[item.type]"></span>{{item.name}}
</span>
</li>
</ul>
</div>
<div class="foods-wrapper" ref="foodWrapper">
<ul>
<li v-for="item in goods" class="food-list food-list-hook">
<h1 class="title">{{item.name}}</h1>
<ul>
<li v-for="food in item.foods" class="food-item" @click="selectFood(food, $event)">
<div class="icon">
<img :src="food.icon" alt="" width="57">
</div>
<div class="content">
<h2 class="name">{{food.name}}</h2>
<p class="desc">{{food.description}}</p>
<div class="extra">
<span class="count">月售{{food.sellCount}}</span><span class="count">好评{{food.rating}}</span>
</div>
<div class="price">
<span class="now">¥{{food.price}}</span><span class="old"
v-show="food.oldPrice">¥{{food.oldPrice}}</span>
</div>
<div class="cartControl-wrapper">
<cartControl :food="food" @increment="incrementTotal"></cartControl>
</div>
</div>
</li>
</ul>
</li>
</ul>
</div>
6.1 $refs
的使用是vue 2 操作dom的一种方式
ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素; 如果用在子组件上,引用就指向组件实例:
_initScroll(){
//初始化scroll区域
this.menuScroll = new BScroll(this.$refs.menuWrapper, {
click: true //结合BScroll的接口使用,是否将click事件传递,默认被拦截了
});
this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
probeType: 3 //结合BScroll的接口使用,3实时派发scroll事件,探针的作用
});
//结合BScroll的接口使用,监听scroll事件(实时派发的),并获取鼠标坐标,当滚动时能实时暴露出scroll
this.foodsScroll.on('scroll', (pos) => { //事件的回调函数
this.scrollY = Math.abs(Math.round(pos.y));//滚动坐标会出现负的,并且是小数,所以需要处理一下,实时取得scrollY
}) }
vue中更改数据,DOM会跟着做映射,但vue更新DOM是异步的,用 $nextTick
()来确保Dom变化后能调用到_initScroll()方法。调用_initScroll()方法能计算内层ul的高度,当内层ul的高度大于外层wrapper的高度时,可以实现滚动。
6.2 左右两边联动
在vue实例生命周期的开始created分别加载
_initScroll
和_calculateHeight
通过
_calculateHeight
计算foods内部每一个块的高度,组成一个数组listHeight在_initScroll里面,设置了bscroll插件的一个监听事件scroll,将food区域当前的滚动到的位置的y坐标设置到一个vue实例属性scrollY
this.scrollY = Math.abs(Math.round(pos.y));
通过计算属性currentIndex,获取到food滚动区域对应的menu区域的子块的索引,然后通过设置一个class来做样式切换变化
:class="{'current':currentIndex === index}
,实现联动另外当点击menu 区域的时候,会触发selectMenu事件,也会根据点击到的menu子块的索引然后去触发food区域滚动到对应的高度区块区间
this.foodsScroll.scrollToElement(el, 300);scrollToElement():是better-scroll中的方法,滚动到某个元素,el(必填)表示 dom 元素,time 表示动画时间,offsetX 和 offsetY 表示坐标偏移量,easing 表示缓动函数
这样完成整个对应
_calculateHeight()方法计算各个右侧区间的高度
_calculateHeight(){
let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook'); //获取每一个food的dom对象
let height = 0;
this.listHeight.push(height); //初始化第一个高度为0
for (let i = 0; i < foodList.length; i++) {
let item = foodList[i]; //每一个item都是刚才获取的food的每一个dom
height += item.clientHeight; //主要是为了获取每一个foods内部块的高度
this.listHeight.push(height);
}
}
}
实时取得scrollY的值后,需要与左边进行映射,利用计算属性:
computed: {
currentIndex(){ //计算到达哪个区域的区间的时候的对应的索引值
for (let i = 0; i < this.listHeight.length; i++) {
let height1 = this.listHeight[i]; //当前menu子块的高度
let height2 = this.listHeight[i + 1]; //下一个menu子块的高度
//滚动到底部的时候,height2为undefined,需要考虑这种情况
//需要确定是在两个menu子块的高度区间
if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {
return i; //返回这个menu子块的索引
}
}
return 0;
},
selectFoods() { //自动将所有的goods.food添加一个count属性,方便做数量运算
let foods = [];
this.goods.forEach((good) => {
good.foods.forEach((food) => {
if (food.count) {
foods.push(food);
}
});
});
return foods;
}
}
:class="{'current':currentIndex === index}"当currentIndex === index时才设置current这个class
点击左侧 ,右侧响应:
关于在selectMenu中点击,在pc界面会出现两次事件,在移动端就只出现一次事件的问题:
原因:bsScrooler会监听事件(例如touchmove,click之类),并且阻止默认事件(prevent stop),并且他只会监听移动端的,pc端的没有监听
在pc页面上 bsScroller也派发了一次click事件,原生也派发了一次click事件
//bsScroll的事件,有_constructed: true
MouseEvent {isTrusted: false, _constructed: true, screenX: 0, screenY: 0, clientX: 0…}
//pc的事件
MouseEvent {isTrusted: true, screenX: -1867, screenY: 520, clientX: 53, clientY: 400…}
解决:针对bsScroole的事件,有_constructed: true,所以做处理,return掉非bsScroll的事件
selectMenu(index, event){
if (!event._constructed) { //去掉自带的click事件点击,即pc端直接返回
return;
}
let foodsList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
let el = foodsList[index];
//类似jump to的功能,通过这个方法,跳转到指定的dom
this.foodsScroll.scrollToElement(el, 300);
},
7.shopcart组件
也是采用flex布局,右侧部分固定宽度(flex 0 0 105px),左边自适应宽度(flex 1)
采用固定定位,定位在底部(position fixed)
横向排列display:inline-block
包含购物车图标的div超出了父元素的高度,我们使用position:relative,并设置top为负来实现
box-sizing: border-box; 则div 设置的宽高将包含 边框及 padding
border-radius 50%,形成一个圆
选择了多少商品:定义成数组,底栏其余部分的变化都基于这个对象的变化而变化
selectFoods: {
type: Array,
default() {
return [{price: 20, count: 2}];
}
}
计算部分(都基于selectFoods进行相应计算)computed中的函数可以直接在Tempplate中以指针的形式引用
computed: {
totalPrice() {//计算总价,超过起送额度后提示可付款
let total = 0;
this.selectFoods.forEach((food) => {
total += food.price * food.count;
});
return total;
},
totalCount() {//计算选中的food数量,在购物车图标处显示,采用绝对定位,top:0;right:0;显示在购物车图标右上角
let count = 0;
this.selectFoods.forEach((food) => {
count += food.count;
});
return count;
}
控制底部右边内容随food的变化而变化,payDesc()控制显示内容,payClass()添加类调整显示样式
在template中
<div class="pay" :class="payClass">
{{payDesc}}
</div>
在computed中:
payDesc() {
if (this.totalPrice === 0) {
return `¥${this.minPrice}元起送`; //这里使用的是es6中的反引号
} else if (this.totalPrice < this.minPrice) {
let diff = this.minPrice - this.totalPrice;
return `还差¥${diff}元起送`;
} else {
return '去结算'; //单引号,单引号和反引号不同
}
},
payClass() {
if (this.totalPrice < this.minPrice) {
return 'not-enough';
} else {
return 'enough';
}
}
总结:通过以上学习我们能发现,selectFoods()的变化起着关键作用,它的变化会引起DOM的变化,并最终体现到界面上,而我们不用关注DOM内部的具体实现,这就是vue的一大好处。如果采用jQuery完成这些功能会略显繁杂。
8 cartcontrol组件,它是shopcart的子组件
可以给按钮增加padding,方便用户点击
this.foodScroll = new BScroll(this.$refs.foodWrapper, {
probeType: 3,
click: true
});
click: true
是否派发click事件
通过import Vue from 'vue';使用set接口,通过vue.set()添加属性,当它变化时就能被检测到,从而父组件能获取到count值(遍历选中的商品时使用)
methods: {
addCart(event) {
if (!event._constructed) {
// 去掉自带click事件的点击
return;
}
if (!this.food.count) {
Vue.set(this.food, 'count', 1);
} else {
this.food.count++;
}
// event.srcElement.outerHTML
this.$emit('increment', event.target); // 子组件通过 $emit触发父组件的方法 increment 还
},
decreaseCart(event) {
if (!event._constructed) {
// 去掉自带click事件的点击
return;
}
this.food.count--;
}
}
};
9 为减号按钮添加平移、滚动的动画
<transition name="fade"> //减号和数字平移动画
<div class="cart-decrease" v-show="food.count>0" @click.stop.prevent="decreaseCart($event)">
<transition name="inner"> //数字滚动动画
<span class="inner iconfont icon-jian"></span>
</transition>
</div>
</transition>
&.fade-enter-active, &.fade-leave-active {
transition: all 0.4s linear <--过渡效果的 CSS 属性的名称、过渡效果需要多少时间、速度效果的速度曲线-->
}
&.fade-enter, &.fade-leave-active {
opacity: 0
transform translate3d(24px, 0, 0) //这样可以开启硬件加速,动画更流畅,3D旋转,X轴位移24px
}
.inner
display inline-block <--设置成inline-block才有高度,才能有动画-->
line-height 24px
font-size 24px
vertical-align top
color rgb(0, 160, 220, 0.2)
&.inner-enter-active, &.inner-leave-active {
transition: all 0.4s linear
transform: rotate(0)
}
&.inner-enter, &.inner-leave-active {
opacity: 0
transform rotate(180deg)
}
10 购物小球(抛物线小球)
通过两个层来控制小球,外层控制一个方向的变化,内层控制另外一个方向的变化(写两层才会有抛物线的效果),采用fixed布局(是相对于视口的动画)
<div class="ball-container">
<div v-for="ball in balls">
<transition name="drop" @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">//后面三个为钩子
<div v-show="ball.show" class="ball">
<div class="inner inner-hook">
</div>
</div>
</transition>
</div>
</div>
在addCart()方法(在cartControl组件里)中添加(子组件通过 $emit触发父组件的方法 increment)
this.$emit('increment', event.target);
在父组件(goods组件)的template中写入
<cartControl :food="food" @increment="incrementTotal"></cartControl>
在(goods组件)method中写入this.$refs.shopCart指向shopCart组件,该组件中有drop()方法(父组件访问子组件的方法)
首先在HTML中指定ref
当点击“加号”按钮时,cartControl组件通过emit触发父组件goods中的increment方法,并将event.target对象传入,increment方法将target传入shopCart子组件中的drop方法,所以drop方法能获得用户点击按钮的元素,即能获取点击按钮的位置
<shopCart :select-foods="selectFoods" :delivery-price="seller.deliveryPrice"
:min-price="seller.minPrice" ref="shopCart"></shopCart>
incrementTotal(target) {
this.$refs.shopCart.drop(target);
}
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;
}
}
}
补充:
但是在vue2.0中$dispatch 和 $broadcast被弃用,因为基于组件树结构的事件流方式实在是让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱,并且这只适用于父子组件间的通信。官方给出的最简单的升级建议是使用集中的事件处理器,而且也明确说明了 一个空的vue实例就可以做到,因为Vue 实例实现了一个事件分发接口在vue2.0中在初始化vue之前,给data添加一个 名字为eventhub 的空vue对象
某一个组件内调用事件触发
this.$root.eventHub.$emit('eventName', event.target);
另一个组件内调用事件接收, 在组件销毁时接除事件绑定,使用$off方法
created:{
this.$root.eventHub.$on('eventName',(target) => {
this.functionName(target)
});
},
method:{
functionName(target) {
console.log(target);
}
}
因为小球是有去无回的动画过程,这里采用vue中提供的钩子
<transition name="drop" @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
对应的方法写在methods中
beforeEnter(el) { //找到所以设为true的小球
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; //点击的按钮与小球(fixed)之间x方向的差值
let y = -(window.innerHeight - rect.top - 22);
el.style.display = ''; //设置初始位置前,手动置空,覆盖之前的display:none,使其显示
el.style.webkitTransform = `translate3d(0,${y}px,0)`; //外层元素做纵向的动画,y是变量
el.style.transform = `translate3d(0,${y}px,0)`;
let inner = el.getElementsByClassName('inner-hook')[0];//内层元素做横向动画,inner-hook(用于js选择的样式名加上-hook,表明只是用 //于js选择的,没有真实的样式含义)
inner.style.webkitTransform = `translate3d(${x}px,0,0)`;
inner.style.transform = `translate3d(${x}px,0,0)`;
}
}
},
enter(el) {
// let rf = el.offestHeight;
this.$nextTick(() => {//异步执行
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)';
});
},
afterEnter(el) {
let ball = this.dropBalls.shift(); //取到做完动画的球,再置为false,即重置,它还可以接着被利用
if (ball) {
ball.show = false;
el.style.display = 'none';
}
}
关于cubic-bezier(0.49, -0.29, 0.75, 0.41)
,是动画抛物曲线(贝塞尔曲线)的配置,可以利用http://cubic-bezier.com/#.23,1.14,.83,.67 进行曲线调试
.ball-container
.ball
position fixed
left 32px
bottom 22px
z-index 200
transition: all 0.6s cubic-bezier(0.49, -0.29, 0.75, 0.41)
.inner
width 16px
height 16px
border-radius 50%
background rgb(0, 160, 220)
transition: all 0.4s linear
点击小球时有点卡,可采用异步执行的方式回调
incrementTotal(target) {
this.$nextTick(()=>{
this.$refs.shopCart.drop(target);
})
}
关于nextTick可以参考:https://segmentfault.com/a/1190000008570874
官方解释:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
11 什么时候需要用的Vue.nextTick()
a.你在Vue生命周期的created()
钩子函数进行的DOM操作一定要放在Vue.nextTick()
的回调函数中。原因是什么呢,原因是在created()
钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()
的回调函数中。与之对应的就是mounted
钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。
b.在数据变化后要执行的某个操作
,而这个操作
需要使用随数据改变而改变的DOM结构的时候,这个操作
都应该放进Vue.nextTick()
的回调函数中。
12 购物车详情
transform translate3d(0, -100%, 0),可以使详情页高度随内容的增加而增加
12.1 针对购物车显示的详情页添加滑动插件
listShow() {
if (!this.totalCount) {
this.fold = true;
return false;
}
let show = !this.fold;
if (show) {//如果显示详情页
this.$nextTick(() => {//数据变化后,DOM并没有立即生效,而BScroll严重依赖于DOM,所以使用nextTick
if (!this.scroll) {//如果实例不存在,新建
this.scroll = new BScroll(this.$refs.listContent, {
click: true
});
} else {//实例存在,直接调用refresh接口
this.scroll.refresh();
}
});
}
return show;
}
}
12.2 购物车清空
setEmpty() {
this.selectFoods.forEach((food) => {
food.count = 0
})
}
【vue】饿了么项目-goods商品列表页开发的更多相关文章
- vue 饿了么项目笔记
vue 饿了么项目 1.图标字体引用 链接 2.scss 二三倍图切换 1像素边框 链接 3.better-scroll 4.布局 商品主页面 <div id="app"&g ...
- (生鲜项目)07. api view实现商品列表页
第一步: 环境配置 1. DRF官网: https://www.django-rest-framework.org/ 仔细查看自己当前的python版本以及django版本是否支持DRF, 然后就看看 ...
- (生鲜项目)06. django的view实现商品列表页
使用原始的django的View来返回json格式的商品列表 目的是回顾一些django的基础知识, 好与后面的RESTful做对比 goods.views_base.py from django.v ...
- (生鲜项目)08. ModelSerializer 实现商品列表页, 使用Mixin来实现返回, 以及更加方便的ListAPIView, 以及分页的设置
第一步: 学会使用ModelSerializer, 并且会使用ModelSerializer相互嵌套功能 1. goods.serializers.py from rest_framework imp ...
- 5- vue django restful framework 打造生鲜超市 -完成商品列表页(上)
使用Python3.6与Django2.0.2(Django-rest-framework)以及前端vue开发的前后端分离的商城网站 项目支持支付宝支付(暂不支持微信支付),支持手机短信验证码注册, ...
- 6- vue django restful framework 打造生鲜超市 -完成商品列表页(下)
Vue+Django REST framework实战 搭建一个前后端分离的生鲜超市网站 Django rtf 完成 商品列表页下 drf中的request和response drf对于django的 ...
- react 从商品详情页返回到商品列表页,列表自动滚动上次浏览的位置
现状:目前从商品详情页返回到商品列表页,还需要再去请求服务数据,还需要用户再去等待获取数据的过程,这样用户体验非常不好, 遇到的问题: 1:如何将数据缓存, 2:如何获取和保存列表滑动的高度, 3:判 ...
- 007商城项目:商品列表查询-需求分析,以及Spinmvc的访问知识
我们之前已经整合了ssm框架并且调试已经好了,接下来我们实现商品列表的查询. 我们先进入到首页: 方法如下: 我们看到我们把所有的jsp页面都是放在: 这些页面都是放在WEB-IN下面的,也就是说这些 ...
- Spring Boot 构建电商基础秒杀项目 (九) 商品列表 & 详情
SpringBoot构建电商基础秒杀项目 学习笔记 ItemDOMapper.xml 添加 <select id="listItem" resultMap="Bas ...
随机推荐
- photoshop cc 2014 下载安装及汉化资源及切图简要使用教程
这是百度经验上一个pscc 2014 版本的下载安装汉化教程,亲测有效: http://jingyan.baidu.com/article/647f0115bce3847f2148a80c.html ...
- 面向对象(基础oop)之结构与数组高级
大家好,我叫李京阳,,很高兴认识大家,之所以我想开一个自己的博客,就是来把自己所了解的知识点通过自己的话写一下,希望被博客园的朋友们点评和一起讨论一下,也希望从博客园中多认识一些软件开发人员!现在我开 ...
- [编程] C语言Linux系统编程-等待终止的子进程(僵死进程)
1.等待终止的子进程(僵死进程): 如果一个子进程在父进程之前结束,内核会把子进程设置为一个特殊的状态,处于这种状态的进程称为僵死进程 当父进程获取了子进程的信息后,子进程才会消失. pid_t wa ...
- VPS虚拟化架构OpenVZ、KVM、Xen、Hyper-V的区别
1.OpenVZ OpenVZ(简称OVZ)采用SWsoft的Virutozzo虚拟化服务器软件产品的内核,是基于Linux平台的操作系统级服务器虚拟化架构.这个架构直接调用宿主机(俗称:母机)中的内 ...
- zoj 1760 查找
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=760 撸了个二分查找 #include<iostream> #inc ...
- python3 利用pip安装ipython notebook
python 3.6 ,因为不想安装anaconda,但是ipyhon notebook一直出错,所以搞好后特此纪念一下. 命令行输入pip install ipython[all], 安装ipyth ...
- 理解webpack4.splitChunks之其余要点
splitChunks除了之前文章提到的规则外,还有一些要点或是叫疑惑因为没有找到官方文档的明确说明,所以是通过我自己测试总结出来的,只代表我自己的测试结果,不一定正确. splitChunks.ca ...
- ionic —指令
引用 <!--1.引入 ionic css和js--> <!--2.定义ng-app--> <!--3.定义 angular.module('myAPp',['ionic ...
- sql-pivot
PIVOT PIVOT运算符用于在列和行之间进行数据旋转或透视转换,同时执行聚合运算 ,,) Order By empid asc Select * From ( Select empid,YEAR( ...
- 弧形菜单2(动画渐入)Kotlin开发(附带java源码)
弧形菜单2(动画渐入+Kotlin开发) 前言:基于AndroidStudio的采用Kotlin语言开发的动画渐入的弧形菜单...... 效果: 开发环境:AndroidStudio2.2.1+gra ...