vue2.0 饿了么学习笔记(17)seller组件开发
BScroll回顾: 外层有一个容器,定义了视口的高度,内层div可以被内容自动撑高,当内层容器高度超过视口高度时,就会出现滚动
1)接收传递进来的seller数据,并编写界面和样式
props: {
//APP.vue的routerview中已经将seller传进来了,这里只需要接收就好
seller: {
type: Object
}
},
<div class="overview">
<h1 class="title">{{seller.name}}</h1>
<div class="desc border-1px">
<star :size="36" :score="seller.score"></star>
<span class="text">({{seller.ratingCount}})</span>
<span class="text">月售{{seller.sellCount}}单</span>
</div>
<ul class="remark">
<li class="block">
<h2>起送价</h2>
<div class="content">
<span class="stress">{{seller.minPrice}}</span>元
</div>
</li>
<li class="block">
<h2>商家配送</h2>
<div class="content">
<span class="stress">{{seller.deliveryPrice}}</span>元
</div>
</li>
<li class="block">
<h2>平均配送时间</h2>
<div class="content">
<span class="stress">{{seller.deliveryTime}}</span>元
</div>
</li>
</ul>
<div class="favorite" @click="toggleFavorite($event)">
<i class="icon-favorite" :class="{'active':favorite}"></i> <!-- 对应是否收藏两种样式-->
<span>{{favoriteText}}</span> <!-- 有没有选中对应不同的文本,所以这里要绑定一个变量,放到data中 -->
</div>
</div>
添加响应的css
.seller
position absolute
top 174px
bottom 0
left 0
width 100%
overflow hidden
.overview
padding 18px
position relative
.title
margin-bottom 8px
line-height 14px
color rgb(7, 17, 27)
font-size 14px
.desc
padding-bottom 18px
font-size 0
border-1px(rgba(7, 17, 27, 0.1))
.star
display inline-block
vertical-align top
margin-right 8px
.text
display inline-block
vertical-align top
margin-right 12px
line-height 18px // 不能为父元素设置line-heigth,要不然组件会被撑高
font-size 10px
color rgb(77, 85, 93)
.remark
display flex
padding-top 18px
.block
flex 1
text-align center
border-right 1px solid rgba(7, 17, 27, 0.1)
&:last-child
border none
h2
margin-bottom 4px
line-height 10px
font-size 10px
color rgb(147, 153, 149)
.content
line-height 24px
font-size 10px
color rgb(7, 17, 27)
.stress
font-size 24px
2)添加公告与活动部分,先添加一个split组件,在添加内容,不要忘了把图片拷贝过来
<div class="bulletin">
<h1 class="title">公告与活动</h1>
<div class="content-wrapper border-1px">
<p class="content">{{seller.bulletin}}</p>
</div>
<ul v-if="seller.supports" class="supports">
<li class="support-item border-1px" v-for="(item,index) in seller.supports" :key="(item.id,index.id)">
<span class="icon" :class="classMap[seller.supports[index].type]"></span>
<span class="text">{{seller.supports[index].description}}</span>
</li>
</ul>
</div>
<split></split>
created() {
this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
}
编写相应的样式:
.supports
.support-item
padding 16px 12px
border-1px(rgba(7, 17, 27, 0.1))
font-size 0
&:last-child
border-none()
.icon
display inline-block
width 16px
height 16px
vertical-align top
margin-right 6px
background-size 16px 16px
background-repeat no-repeat
&.decrease
bg-image('decrease_4')
&.discount
bg-image('discount_4')
&.guarantee
bg-image('guarantee_4')
&.invoice
bg-image('invoice_4')
&.special
bg-image('special_4')
.text
display inline-block
font-size 12px
line-height 16px
color rgb(7, 17, 27)
3)页面过长,将BScroll引入进来
3.1 .初始化BScroll语句放在created(),按照DOM的生命周期来说,执行到DOM的时候不能保证DOM已经被渲染了,在goods.vue中把BScroll初始化语句写在created()中,是因为前半部分先执行了获取api数据的语句,在seller中,数据是通过props传进来的,不能保证数据是否已经被渲染了
3.2 初始化BScroll语句放在ready中又不起作用,DOM被渲染完之后,ready是自动被执行的,在ready中写的代码都可以安全的去使用DOM,但是高度计算不对,无法正确滚动,原因:seller是异步获取的,但是我们的内容都是靠seller里的数据撑开的,,所以一开始内容肯定是小于我我们定义的wrapper的,所以没有被撑开
3.3 将其放入watch:{}中可以watch到seller的变化,将初始化语句写成一个方法,在watch中进行调用
watch: {
'seller'() {
this._initScroll();
this._initPics();
}
},
下面是初始化的函数:
_initScroll() {
if (!this.scroll) {
this.$nextTick(() => {
this.scroll = new BScroll(this.$refs.seller, {click: true});
});
} else {
this.scroll.refresh();
}
},
_initPics() {
if(this.seller.pics) {
let picWidth = 120;
let margin = 6;
let width = (picWidth + margin) * this.seller.pics.length - margin;
this.$refs.picList.style.width = width + 'px'; //不要忘记加单位
if(!this.picScroll){
this.$nextTick(() => {
this.picScroll = new BScroll(this.$refs.picWrapper, {
scrollX: true, //表示横向滚动
eventPassthrough: 'vertical' //横向滚动图片的时候就忽略垂直方向的滚动
});
});
} else {
this.picScroll.refresh();
}
}
}
3.4 .每次切换商家页面时,DOM被重新渲染,又不能滚动了,因为在watch中只能观测到seller的变化,切换区块的时候seller没有变化,因为只有我们刷新界面,seller发生变化时,watch才会执行,所以单纯的切换界面seller中的watch不会被执行,所以BScroll就不会初始化。我们在ready方法中重新加入初始化语句
watch: {
'seller'() {
this._initScroll();
this._initPics();
}
},
ready() {
this._initScroll();
this._initPics();
//设定ul的宽度
},
之后,我们发现之前的情况是切换之后不能滚动,现在的新问题是一开始(没切换界面之前)就不能滚动了,切换之后就可以滚动了;
一定要为初始化函数_initScroll()和this._initPics()中的nextTick()下的添加if-else语句,因为ready的执行时机要先于watch中的seller,然后我们在执行seller中的initScroll的时候就会发现BScroll已经被初始化了,所以initScroll失效,即使在watch中观察到变化也只能什么都不做,所以我们将一个else选项,对BScroll进行刷新,完成
6.商家实景区块:添加图片,设置样式,横向排列并实现横向滚动,ul是外层的宽度,并不是真实的li撑开的宽度,使用BScroll实现滚动,添加—_initPic()方法,并把它添加到watch和ready中
pic-wrapper是固定宽度的视口的大小,当里面的ul超过视口宽度的时候就会出现滚动
<div class="pics">
<h1 class="title">商家实景</h1>
<div class="pic-wrapper" ref="picWrapper">
<ul class="pic-list" ref="picList">
<li class="pic-item" v-for="pic in seller.pics" :key="pic.id">
<img :src="pic" width="120" height="90">
</li>
</ul>
</div>
</div>
<split></split>
_initPics中根据图片的宽度,margin和数量,计算出内层ul的宽度,并设置ul的宽度
_initPics() {
if (this.seller.pics) {
let picWidth = 120;
let margin = 6;
let width = (picWidth + margin) * this.seller.pics.length - margin;
this.$refs.picList.style.width = width + 'px'; //不要忘记加单位
if (!this.picScroll) {
this.$nextTick(() => {
this.picScroll = new BScroll(this.$refs.picWrapper, {
scrollX: true, //表示横向滚动
eventPassthrough: 'vertical' //横向滚动图片的时候就忽略垂直方向的滚动
});
});
} else {
this.picScroll.refresh();
}
}
}
css样式:
.pics
padding 18px
.title
margin-bottom 12px
line-height 14px
color rgb(7, 17, 27)
font-size 14px
.pic-wrapper
width 100%
overflow hidden
white-space nowrap /*不产生折行*/
.pic-list
font-size 0
.pic-item
display inline-block
margin-right 6px
width 120px
height 90px
&:last-child
margin 0
添加商家信息
<div class="info">
<div class="title border-1px">商家信息</div>
<ul>
<li class="info-item" v-for="info in seller.infos" :key="info.id">{{info}}</li>
</ul>
</div>
</div>
7.添加收藏按钮,设置:active样式(红,白)和字体的变化(收藏和未收藏)
<div class="favorite" @click="toggleFavorite($event)">
<i class="icon-favorite" :class="{'active':favorite}"></i> <!-- 对应是否收藏两种样式-->
<span>{{favoriteText}}</span> <!-- 有没有选中对应不同的文本,所以这里要绑定一个变量,放到data中 -->
</div>
favorite是一个变量,我们要在data里面观测它
data() {
return {
// favorite: false, //默认没有被收藏,从localStorge中取读取,不是一个默认值了
favorite: (() => {
return loadFromlLocal(this.seller.id, 'favorite', false);
})()
};
},
computed: {
favoriteText() {
return this.favorite ? '已收藏' : '收藏';
}
},
添加css样式:
.favorite
position absolute
right 11px
top 18px
width 50px /* 设定宽度改变样式的时候就不会发生偏移*/
text-align center
.icon-favorite
display block
margin-bottom 4px
line-height 24px
font-size 24px
width 50px
color #d4d6d9
&.active
color rgb(240,20,20)
.text
line-height 10px
font-size 10px
color rgb(77,85,93)
methods中添加逻辑点击事件:
toggleFavorite(event) {
if (!event._constructed) {
return;
}
this.favorite = !this.favorite;
//这样写取法区分商家id,不同商家的状态一样
//localStorage.favorite = this.favorite;
saveToLocal(this.seller.id, 'favorite', this.favorite);
},
每一个商家都有一个唯一的id,这个id存在url中,所以封装一个函数,将url解析成对象的模式
/**
* 解析url参数
* Created by yi on 2016-12-28.
* @return Object {id:12334}
*/
export function urlParse() {
let url = window.location.search;
let obj = {};
let reg = /[?&][^?&]+=[^?&]]+/g;
let arr = url.match(reg);
// ['?id=123454','&a=b']
if (arr) {
arr.forEach((item) => {
let tempArr = item.substring(1).split('=');// 先分割取到id=123454,之后用=号分开
let key = tempArr[0];
let val = tempArr[1];
obj[key] = val;
});
}
// return obj;
return {id: 123123};
};
之后,在App.vue组件中引入urlParse,并在data中获取data
刷新之后,收藏样式就会消失,将收藏的信息添加到localStorge中,创建store.js实现数据的存取,专门存取不同商家的id
//savaToLocal(this.seller.id, 'favorite', this.favorite);存取
export function saveToLocal(id, key, value) { //存储到localStorge
let seller = window.localStorage.__seller__;
if (!seller) { //没有seller的时候,初始化,定义一个seller对象,并给他设定一个id
seller = {};
seller[id] = {}; // 每个id下都是一个单独的obj
} else {
seller = JSON.parse(seller); // JSON 字符串转换为对象
if (!seller[id]) { //判断是否有当前这个商家
seller[id] = {};
}
}
seller[id][key] = value; // 将key和value存到id这个对象的下边
//将一个JavaScript值(对象或者数组)转换为一个 JSON字符串
window.localStorage.__seller__ = JSON.stringify(seller);
}
//loadFromlLocal(this.seller.id, 'favorite', false);读取
export function loadFromlLocal(id, key, def) { //读取,读不到的时候传入一个default变量
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;
}
引入:
import {saveToLocal, loadFromlLocal} from 'common/js/store.js';
在toggleFavorite中使用这两个方法
toggleFavorite(event) {
if (!event._constructed) {
return;
}
this.favorite = !this.favorite;
//这样写取法区分商家id,不同商家的状态一样
//localStorage.favorite = this.favorite;
saveToLocal(this.seller.id, 'favorite', this.favorite);
},
data() {
return {
// favorite: false, //默认没有被收藏,从localStorge中取读取,不是一个默认值了
favorite: (() => {
return loadFromlLocal(this.seller.id, 'favorite', false);
})()
};
}