七,goods商品列表页开发
1. 布局编写
如果绝对定位(position属性的值为absolute)的元素没有“positioned”祖先元素,那么它是相对于文档的 body 元素,并且它会随着页面滚动而移动。这里我们把goods设成绝对定位。
menu-wrapper除了设置flex,还要设置width为了安卓手机下兼容。
在css中尽量用class而不用#标签,这是为了优化速度。
垂直居中用display:table表现最好。
建议在<img >标签中写好width height样式。
本例中有嵌套使用flex布局。
html中两个span拼在一起没有换行是为了解决空隙,当然也可以用我们以前用过的font-size: 0的方法。
2. better-scroll
老师自己根据iscroll改进重写的库,npm安装。注意安装依赖一定要先终止监听进程再安装。better-scroll.
a. 实现滚动
获取DOM在vue1.0中要用v-el,注意写法:模板元素中,写v-el: v-el:name-name1(名字不能用驼峰,只能用下划线连接,冒号后面不要有空格); 在 js中取到dom的写法: this.$els.nameName1 (名字在这里就需要使用驼峰命名),从而获取到DOM对象。对于给子组件传参的命名也是遵循这个规则。
vue中dom的加载是个异步过程,当直接调用_initScroll()的时候 dom并没有更新,_initScroll中高度的计算会出现问题,因此用到了vue中的$nextTick接口(数据变化到 dom 的变化需要经历一个 tick),注意要放在Ajax回调函数中。
https://vuefe.cn/v2/api/#Vue-nextTick-callback-context
vue1.0升级 vue2.0: 去掉v-el,用ref="menuWrapper"代替v-el:menu-wrapper js: this.menuScroll = new BScroll(this.$els.menuWrapper, {}); 替换为: this.menuScroll = new BScroll(this.$refs.menuWrapper, {});
//template
<div class="menu-wrapper" v-el:menu-wrapper>
<div class="foods-wrapper" v-el:foods-wrapper>
...
//script
created() {
...
if (response.errno === ERR_OK) {
this.goods = response.data;
this.$nextTick(() => {
this._initScroll();
});
}
});
},
methods: {
_initScroll() {
this.menuScroll = new BScroll(this.$els.menuWrapper, {});
this.foodsScroll = new BScroll(this.$els.foodsWrapper, {});
}
}
用vuex做滚动的一个慕课网QA.
b. 左右联动
在nextTick中计算每个区块的高度,用来判断当前区块属于哪一个menu菜单。
在Dom中添加class,注意命名为“-hook”,表示没有具体样式仅仅是选择。
注意使用z-index生效的前提是已定位元素(positioned)。
better-scroll使用时会默认阻止掉点击事件,可以用click:true
,派发一个点击事件,但是有个问题,在手机模拟器中,触发1次点击事件,网页版的触发2次点击事件,原生的点击事件会被监听到,解决:可以传参$event
,通过$event._constructed
是否为true
来判断是否为自己派生的事件,如果原生事件没有这个属性。better-scroll的最新版已经修复了这个问题。
better-scroll有scrollToElement()方法,直接传入DOM元素和动画时间。
//good.vue
<li v-for="item in goods" class="menu-item" :class="{'current':currentIndex===$index}"
@click="selectMenu($index, $event)"> ..</li>
<li v-for="item in goods" class="food-list food-list-hook">..</li>
...
export default {
props: {
seller: {
type: Object
}
},
data() {
return {
goods: [],
listHeight: [],
scrollY: 0
};
},
computed: {
currentIndex() {
for (let i = 0; i < this.listHeight.length; i++) {
let height1 = this.listHeight[i];
let height2 = this.listHeight[i + 1];
if (!height2 || (height1 <= this.scrollY && this.scrollY < height2)) {
return i;
}
}
return 0;
}
},
created() {
this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
this.$http.get('api/goods').then((response) => {
response = response.body;
if (response.errno === ERR_OK) {
this.goods = response.data;
console.log(this.goods);
this.$nextTick(() => {
this._initScroll();
this._calculateHeight();
});
}
});
},
methods: {
selectMenu(index, event) {
if (!event._constructed) {
return;
}
let foodList = this.$els.foodsWrapper.getElementsByClassName('food-list-hook');
let el = foodList[index];
this.foodsScroll.scrollToElement(el, 300);
},
_initScroll() {
this.menuScroll = new BScroll(this.$els.menuWrapper, {
click: true
})
;
this.foodsScroll = new BScroll(this.$els.foodsWrapper, {
probeType: 3
});
this.foodsScroll.on('scroll', (pos) => {
this.scrollY = Math.abs(Math.round(pos.y));
});
},
_calculateHeight() {
let foodList = this.$els.foodsWrapper.getElementsByClassName('food-list-hook');
let height = 0;
this.listHeight.push(height);
for (let i = 0; i < foodList.length; i++) {
height += foodList[i].clientHeight;
this.listHeight.push(height);
}
}
}
};
height、clientHeight、scrollHeight、offsetHeight区别
注意视频讲解中并没有处理menu-list的联动,比如当右侧foot-list滚动到最底部时左边对应的menu-list是特色粥类却一直在menu栏最底部。vue2.0代码中有pull request修复这个问题。
3.shopcart购物车组件
logo-wrapper 中用border-box这样的IE盒模型,简化计算步骤。price的居中方式没有使用line-height撑满,是为了让border-right实现竖线。
<router-view :seller="seller"></router-view>
数据传输方向:app.vue通过ajax取到数据=>router-view=>good.vue=>shopcart.vue;good定义了props接收seller,shopcart.vue定义了props来接受运费和最低消费。
props里 如果type是object或者Array,那么default就是一个函数。
添加highlight的:class
,使totalCount>0的时候对应div的样式发生变化。统一在content-left中添加highlight更直观方便。
ES6反引号,${}表示变量。
vue中的class,如果根据不同状态有多个类名,比如三选一。那可以用一个计算属性来返回不同类名,然后:class=“computedClassName”
把逻辑写在计算属性里。
目前很多数据是依赖selectFoods来的。下面开发这一部分传入goods数据。
4. cartcontrol组件
实现小按钮功能,购物的控制组件。
为了让用户点击按钮的时候响应区域面积更大,给每个按钮字体图标周围加上padding。
给一个对象添加一个不存在的属性,Vue是检测不了的。这样监控 import Vue from ‘vue’;
,Vue.set(this.foods,"count",1);
才能在dom中实时监控观察并变化。
注意:Vue 2.0 中 props 改为了单向数据流,本课程是基于 Vue 1.0 的,所以 props 还可以使用双向绑定哦。教程中就是在cartcontrol组件中改变foods的count属性,这在Vue2.0是不推荐的。
Vue官方指南 2.0
单向数据流
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。
另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop 。如果你这么做了,Vue 会在控制台给出警告。
注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。
@click函数没有参数的话是默认传入点击事件event。
给减按钮添加过渡动画,外层做平移translate内层做旋转rotate,注意stylus样式书写时候的对齐。其实把两种动画写在一个元素上也是可以的。
教程中在goods组件中用了一个selectedFoods计算属性遍历寻找foods参数传给cartcontrol。有人提出另一个思路,在cartcontrol中emit变化然后在goods中定义对应的method处理,避免多重循环。
5. 购物车小球动画实现
设计素材上没有这部分内容,我们自己想添加的动画。
// shopcart.vue
<div class="ball-container">
<div transition="drop" v-for="ball in balls" v-show="ball.show" class="ball">
<div class="inner"></div>
</div>
</div>
设一个balls数组和dropballs空数组放在data中,这里我们balls数组元素放5个ball,为了能够连续点击购物能有连续的小球飞入解决在前一个小球没有下落后又点击的问题。
教程中,在cartcontrol中dispatch一个事件方法this.$dispatch('cart.add', event.target);
,将control按钮的DOM传入给父组件goods接受,在父组件中定义事件方法events的内容,然后触发shopcart组件的函数(用$ref访问子组件),此时可以在shopcart内部获取操作的dom对象。
当然我们也可以用 vuex 完成兄弟组件的数据共享。
其中$dispatch和events在vue 2.0中均已被废弃。
//goods.vue
<shopcart v-ref: shopcart></shopcart>
events: {
'cart.add'(target){
this._drop(target);
}
}
methods:{
...
_drop(target) {
this.$ref.shopcart.drop(target);
}
}
//shopcart.vue
methods: {
drop(el) {
for (let i = 0; i < this.balls.length; i++) {
let ball = this.ball[i];
if (!ball.show){
ball.show = true;
ball.el = el;
this.dropBalls.push(ball);
return;
}
}
}
}
在shopcart中小球动画用transition钩子在js中写逻辑。这部分代码很绕比较难理解!用了好几次nextTick。
dropBalls里是对balls里小球的同一份引用。balls数组一直都是5个元素。
ball.el是传入的cartcontrol按钮的DOM。js中transition钩子函数中的参数el是transition元素,即滚动的小球。两者不一样的。
beforedrop的时候,display设成空,否则默认是none。因为我们要通过 JS 的方式去实现动画,动画第一帧是需要把元素显示出来的;
afterdrop的时候,因为是你自定义的动画过程,这一切都是你来控制,所以要手动设置 display 为 none。
let rf = el.offsetHeight;
触发浏览器重绘,确保动画正确执行。
bezier曲线做抛物线,网址http://cubic-bezier.com
6.购物车详情页实现
也是一个betterscroll
pay要阻止vue的冒泡事件,加修饰符。
Vue2.0 中好像每个组件template只能有一个跟节点div