<div class="seller" ref="seller"> <div class="seller-content"> <!--overview部分--> <div class="overview"> </div> <split></split> <!--bulltin部分--> <div class="bulletin"> </div> <split></split> <!--pics部分--> <div class="pics"> </div> <split></split> <!--info部分--> <div class="info"> </div> </div> </div>
seller-content
div是可以扩大高度的,可以被内容增长而撑高,而seller
div是固定的高度的,从而造成滚动css
import BScroll from 'better-scroll'; import split from '../split/split'; export default { props: { seller: { //app.vue里面router-view会传入一个seller type: Object } }, watch: { //当seller变更的时候会触发 'seller'() { this.$nextTick(() => { this._initScroll(); this._initPics(); }); } }, mounted() { //在dom被渲染以后触发 this.$nextTick(() => { this._initScroll(); this._initPics(); }); }, methods: { _initScroll() { if (!this.scroll) { this.scroll = new BScroll(this.$refs.seller, { click: true }); } else { this.scroll.refresh(); } }, _initPics() { //详细代码在下面,这里暂时先忽略 } }, components: { split } };
使用watch方法是为了监听数据seller的变化,由于当打开页面的时候,seller是异步获取的,并不必定是可以立刻获取,没有seller的数据,相关dom就没法被渲染,而且bscroll是基于dom执行的,因此须要监听seller的变化而后来从新执行相关的初始化函数html
使用mounted方法是为了保证相关dom渲染完成,由于bscroll是基于dom执行的,可是当切换页面的时候,dom会从新渲染,但未必可以立刻完成,因此须要在mounted方法里面从新执行相关的初始化函数vue
关于watch和mounted的使用要基于vue的生命周期来理解vue生命周期html5
.seller position: absolute top: 174px //给header留空间 bottom: 0 left: 0 width: 100% overflow: hidden //隐藏多余部分,由于须要滚动
<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> <!--列表处理remark内容--> <ul class="remark"> <!--使用一个block块包裹内容--> <li class="block"> <!--标题用h2标签--> <h2>起送价</h2> <!--用content块包裹内容--> <div class="content"> <!--用span表明特殊字体--> <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"> <span class="icon-favorite" :class="{'active':favorite}"></span> <span class="text">{{favoriteText}}</span> </div> </div>
import BScroll from 'better-scroll'; //引入一个利用html5的localstorage的存储模块 import { saveToLocal, loadFromLocal } from 'common/js/store'; export default { props: { seller: { type: Object } }, data() { return { favorite: (() => { //利用localstorage读取这个属性 return loadFromLocal(this.seller.id, 'favorite', false); })()//vue须要返回的data必须是函数,因此加上() }; }, computed: { favoriteText() { //经过favorite的值来计算favoriteText的值 return this.favorite ? '已收藏' : '收藏'; } }, methods: { toggleFavorite(event) { if (!event._constructed) { return; } //经过取反来设置切换 this.favorite = !this.favorite; //利用localstorage存储这个属性 saveToLocal(this.seller.id, 'favorite', this.favorite); } } };
store.js参考:split,formatDate,store,util组件编程
.overview position: relative //设置相对布局参考位置 padding: 18px .title margin-bottom: 8px line-height: 14px color: rgb(7, 17, 27) font-size: 14px .desc padding-bottom: 18px border-1px(rgba(7, 17, 27, 0.1)) font-size: 0 .star display: inline-block margin-right: 8px vertical-align: top //行内对齐 .text display: inline-block margin-right: 12px line-height: 18px vertical-align: top //行内对齐 font-size: 10px color: rgb(77, 85, 93) .remark display: flex //flex布局 padding-top: 18px .block flex: 1 //flex布局等分区域 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, 159) .content //设置该块的公共属性 line-height: 24px font-size: 10px color: rgb(7, 17, 27) .stress //特殊大小再插入class覆盖 font-size: 24px .favorite position: absolute width: 50px //设置一个固定宽度,被text-align使用 right: 11px top: 18px text-align: center .icon-favorite display: block margin-bottom: 4px line-height: 24px font-size: 24px color: #d4d6d9 &.active color: rgb(240, 20, 20) .text line-height: 10px font-size: 10px color: rgb(77, 85, 93)
<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"> <!--使用classMap作对应--> <span class="icon" :class="classMap[seller.supports[index].type]"></span> <span class="text">{{seller.supports[index].description}}</span> </li> </ul> </div>
classMap的原理(classMap使用在以前的header组件使用过Header.vue)segmentfault
seller.supports的结构是这样的数组
"supports": [ { "type": 0, "description": "在线支付满28减5" }, { "type": 1, "description": "VC无限橙果汁全场8折" }, { "type": 2, "description": "单人精彩套餐" }, { "type": 3, "description": "该商家支持发票,请下单写好发票抬头" }, { "type": 4, "description": "已加入“外卖保”计划,食品安全保障" } ],
classMap是['decrease', 'discount', 'special', 'invoice', 'guarantee']
这样的,这个顺序是按照设计须要和数据结构排的,例如type为0对应在线支付满28减5安全
若是要获取对应的class,就能够classMap[seller.supports[index].type]
,例如seller.supports[0].type
就是0,那么就是classMap[0]
就是decrease这个class数据结构
若是要获取对应的description就能够seller.supports[index].description
,例如seller.supports[0].description
就直接知道了supports数组的第一个元素的description属性的值了app
import split from '../split/split'; export default { props: { seller: { type: Object } }, created() { //使用自建的一个classMap数组来对应不一样的类别 this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']; }, components: { split } };
使用created方法是为了在加载的时候第一时间完成,由于vue的渲染须要this.classMap
属性(created是vue的生命周期的第一个)
@import "../../common/stylus/mixin.styl" .bulletin padding: 18px 18px 0 18px .title margin-bottom: 8px line-height: 14px color: rgb(7, 17, 27) font-size: 14px .content-wrapper padding: 0 12px 16px 12px border-1px(rgba(7, 17, 27, 0.1)) .content line-height: 24px font-size: 12px color: rgb(240, 20, 20) .supports .support-item padding: 16px 12px border-1px(rgba(7, 17, 27, 0.1)) font-size: 0 &:last-child border-none() //须要引入一个mixin,处理没边框的问题 .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') //使用mixin处理这些图标 &.discount bg-image('discount_4') &.guarantee bg-image('guarantee_4') &.invoice bg-image('invoice_4') &.special bg-image('special_4') .text line-height: 16px font-size: 12px color: rgb(7, 17, 27)
<div class="pics"> <h1 class="title">商家实景</h1> <!--两个ref绑定picWrapper,picList--> <div class="pic-wrapper" ref="picWrapper"> <!--列表循环输出图片--> <ul class="pic-list" ref="picList"> <li class="pic-item" v-for="pic in seller.pics"> <!--固定图片尺寸--> <img :src="pic" width="120" height="90"> </li> </ul> </div> </div>
ref获取picWrapper是为了滚动图片须要,父DOM是picWrapper,子DOM是picList
ref获取picList是为了计算图片列表的总长度,用来设置图片列表的横向滚动,由于滚动的触发条件是子DOM比父DOM要"大"的时候才会触发,因为图片的数量是未知的,因此须要去计算,而后写入到picList
_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'; //异步绑定滚动 this.$nextTick(() => { if (!this.picScroll) { this.picScroll = new BScroll(this.$refs.picWrapper, { scrollX: true, //bscroll的横向滚动属性 eventPassthrough: 'vertical' //bscroll的过滤垂直滚动 }); } else { this.picScroll.refresh(); } }); }
通常来讲,当某页面自己是垂直滚动的时候,中间的某个DOM须要作横向滚动的话,须要处理滚动坐标的变化,例如手势滚动的时候有x,y坐标,是向上,下,左仍是右会区分
图片列表的总长度是单个图片的宽度+单个图片的外边距的和乘以图片的数量,再减去最后一个图片的外边距
BScroll插件须要增长一个属性scrollX: true
来传入横向的滚动坐标,也须要增长一个属性eventPassthrough: 'vertical'
过滤垂直的滚动,前者已经解释过,后者是由于须要横向滚动的页面是内嵌在垂直滚动的页面的里面的,因此会形成外面的垂直滚动事件也会传入到横向滚动的页面,为了不影响横向滚动,因此须要屏蔽垂直滚动的事件
咱们平时在作这种滚动内嵌滚动的时候也能够参考这个处理方法.
另外异步绑定滚动的常规作法,先判断是否已经有BScroll实例了,而后再肯定是否须要刷新
.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"> <h1 class="title border-1px">商家信息</h1> <ul> <li class="info-item" v-for="info in seller.infos">{{info}}</li> </ul> </div>
.info padding: 18px 18px 0 18px color: rgb(7, 17, 27) .title padding-bottom: 12px line-height: 14px border-1px(rgba(7, 17, 27, 0.1)) font-size: 14px .info-item padding: 16px 12px line-height: 16px border-1px(rgba(7, 17, 27, 0.1)) //1像素的border 的mixin font-size: 12px &:last-child border-none() //没有border的mixin