header 组件

起步

header 组件需完成:

1.头部商家信息的展示

2.完成公告部分

3.弹出层的实现

在这个过程中,很重要的一步是通过异步请求后端数据接口,接收并渲染相关商家数据。

如何实现:使用第三方插件 vue-resource,通过在父组件(App.vue)发送 Ajax 请求获得后端数据,然后通过 header 的 Prop 属性将数据传递给 header 组件。

 

 

知识准备

vue-resource 简介

1)vue-resource 定位
vue-resource 是 Vue.js 的一款插件,它可以通过 XMLHttpRequest 或 JSONP 发起请求并处理响应。也就是说,它可以用于处理前后端数据请求、数据交互,达到与 $.ajax 同等功能。

2)vue-resource 特点:

2-1)支持 Promise API 和 URI Templates

Promise是ES6的特性,Promise的中文含义为“先知”,Promise对象用于异步计算。URI Templates表示URI模板,有些类似于ASP.NET MVC的路由模板。

2-2)支持拦截器(https://github.com/pagekit/vue-resource/blob/develop/docs/http.md#interceptors

拦截器是全局的,拦截器可以在请求发送前和发送请求后做一些处理。拦截器在一些场景下会非常有用,比如请求发送前在headers中设置access_token,或者在请求失败时,提供共通的处理方式。

2-3)支持主流的浏览器:和Vue.js一样,vue-resource除了不支持IE 9以下的浏览器,其他主流的浏览器都支持。

2-4)支持 vue 1.0版本 及 vue 2.0版本

2-5)体积小:vue-resource非常小巧,在压缩以后只有大约14KB,服务端启用gzip压缩后只有5.3KB大小,这远比jQuery的体积要小得多。

PS:

1.支持 promise api 的形式,就是通过同步链式的写法来支持异步的操作。

2.关于 vue-resource 更多知识点:http://www.cnblogs.com/keepfool/p/5657065.html

 

组件

1.什么是组件?
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。

所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。

2.使用组件

1)data 必须是函数

2)组件组合

组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。然而,通过一个良好定义的接口来尽可能将父子组件解耦也是很重要的。这保证了每个组件的代码可以在相对隔离的环境中书写和理解,从而提高了其可维护性和复用性。

在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。看看它们是怎么工作的。

3.Prop

1)使用 Prop 传递数据

组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中。

子组件要显式地用 props 选项声明它预期的数据:

Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 也可以在模板中使用
// 同样也可以在 vm 实例中通过 this.message 来使用
template: '<span>{{ message }}</span>'
})

然后我们可以这样向它传入一个普通字符串:

<child message="hello!"></child>

结果:

hello!

2)动态 Prop

与绑定到任何普通的 HTML 特性相类似,我们可以用 v-bind 来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件:

<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
<!-- v-bind 的缩写语法 -->
<!-- <child :my-message="parentMsg"></child> -->

如果你想把一个对象的所有属性作为 prop 进行传递,可以使用不带任何参数的 v-bind (即用 v-bind 而不是 v-bind:prop-name)。例如,已知一个 todo 对象:

todo: {
text: 'Learn Vue',
isComplete: false
}

然后:

<todo-item v-bind="todo"></todo-item>

将等价于:

<todo-item
    v-bind:text="todo.text"
    v-bind:is-complete="todo.isComplete"
></todo-item>

 

实例生命周期钩子

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

比如 created 钩子可以用来在一个实例被创建之后执行代码:

new Vue({
  data: {
    a: 1
  },
  created: function () {
    // `this` 指向 vm 实例
    console.log('a is: ' + this.a)
  }
})
// => "a is: 1"

也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mounted、updated 和 destroyed。生命周期钩子的 this 上下文指向调用它的 Vue 实例。

 

 

开发

header组件内容

如下图,包括外层组件以及弹出层。在对这两块进行详细设置之前,需要先通过异步请求后端数据接口,接收并渲染相关商家数据。

所以本节有以下三部分知识点:

1.获取数据

2.外层组件

3.弹出层

 

 PS:本节所有代码在文章底部。

 

 

一、获取数据

获取后台商家数据,获得 seller对象数据

1.安装第三方插件 vue-resource,在 main.js 中引用并且注册该插件。

安装方法:npm install vue-resource --save

// 引用
import Vueresource from 'vue-resource'; 
// 全局注册
Vue.use(Vueresource);

 

2.在 App.vue 中定义一个 seller 对象:使用 data() 方法,在函数内返回一个空对象 seller。

Q:为什么是用 data() 方法而不是 data() 对象?

A:因为在 vue.js 中,组件是可以被复用的,也就是说,如果使用对象而不是函数的话,某一组件的修改会影响另外一个组件,所以这里定义的是方法而非函数。更多知识可看官网:data 必须是函数 。

 

3.在 APP.vue 中发送 ajax 请求获取后台商家数据(seller 对象),然后塞给上一步骤的空对象就可以了。

1)什么时候去发送这样一个 ajax 请求?vue实例化的时候都有一个生命周期,其中有个钩子叫 created() ,vue实例被生成后就调用这个函数。一般可以在created函数中调用ajax获取页面初始化所需的数据。一个vue实例被生成后还要绑定到某个html元素上,之后还要进行编译,然后再插入到document中。每一个阶段都会有一个钩子函数,方便开发者在不同阶段处理不同逻辑。

2)在 created 里面通过 get() 方法去获取数据,获取地址是“ /api/seller ”,请求发送以后,调用 then() 方法。then() 方法中的第一个参数是成功的回调,第二个参数是失败的回调。因为这里是模拟后台数据,所以一定是成功的,所以只写成功的回调方法。response 是一个方法,返回的内容不是json对象,而是属性。看 文档 可以知道,body参数返回的数据类型是 object 类型,正是我们所需要的。

3)接着判断 errno 是否为 0,为 0 表示正常,然后返回数据。关于 errno 的设置在 webpack.dev.conf.js 中可以找到。这里定义一个常量 ERR_OK 为 0,是为了阅读代码时更加清晰,知道 0 代表什么,无需通篇查找 errno = 0 的含义,代码更为友好。

 1 const ERR_OK = 0;
 2   //  注册
 3 export default {
 4   created() {
 5     this.$http.get('/api/seller').then(response => {
 6       // get body data
 7       response = response.body;
 8       console.log(response);
 9       if (response.errno === ERR_OK) {
10         this.seller = response.data;
11         console.log(response.data);
12       }
13     });
14   }
15   ...
16 };

 PS:在 network 可以看到有 seller 的数据,在 console 可以看到返回的数据是 object 类型,同时也可以看到这些数据加上了很多 get 和 set 方法。这是 vue 自动给这些字段添加的方法,用于监听这些字段的行为。如果对象某些值被修改时,这些变化就能被监听到,并且及时映射到 DOM 上。关于 get 和 set 方法,在导语1——vue简单介绍的数据响应原理知识点处有提及到。

  

 

接下来进行外层组件和弹出层的设计。这两部分都需要动态获取数据,需要将 seller 对象传递给 header 组件,让 header 组件将其渲染出来。可以通过 v-bind: 将其进行数据绑定,v-bind: 缩写为 : 。

先看一下代码框架:

content-wrapper:外层组件中的上层部分

bulletin-wrapper:外层组件中的公告栏部分

background:header 组件的背景层

detail:弹出层

 

二、外层组件

下图可以看出,外层组件大体分为上下两个部分:上层又可分为左右两部分,左边是图片,右边是商家相关信息;下层是公告栏相关信息。除此之外,还有一个背景层。

 

1.上层部分

 

 1     <div class="content-wrapper">
 2       <div class="avatar">
 3         <img :src="seller.avatar" alt="" width="64" height="64">
 4       </div>
 5       <div class="content">
 6         <div class="title">
 7           <span class="brand"></span>
 8           <span class="name">{{seller.name}}</span>
 9         </div>
10         <div class="description">
11           {{seller.description}}/{{seller.deliveryTime}}分钟送达
12         </div>
13         <div class="support" v-if="seller.supports">
14           <span class="icon" :class="classMap[seller.supports[0].type]"></span>
15           <span class="text">{{seller.supports[0].description}}</span>
16         </div>
17       </div>
18       <div v-if="seller.supports" class="support-count" v-on:click="showDetail">
19         <span class="count">{{seller.supports.length}}个</span>
20         <i class="icon-keyboard_arrow_right"></i>
21       </div>
22     </div>

1)Q:引用头像图片地址,是 :src 而不是 src的原因

A:selller.avatar 一开始是不存在的,因为 seller 是空对象,其数据是通过 ajax 请求异步获取数据然后填充得来的。所以如果直接用 src ,编译就会直接解析 src,此时的 seller 还是是空的,自然就找不到 selller.avatar,就会报错。

 

2)Q:为什么要用 v-if 判断存不存在 supports ?

A:因为这是由数据决定的,有可能该数组不存在。

 

3)Q:不加 v-if 会怎么样?
A:在这里的话,不加 v-if 也能正确显示信息。但是控制台会报错。这是因为在 App.vue 的 html 代码中,我们将 seller 对象传递给了 header 组件,而在下边 JavaScript 代码中, seller 对象一开始被设置为空对象,其数据是通过 ajax 请求异步获取数据然后填充得来的。所以此时 header 组件接收的只是一个空的 seller 对象,本来就是 undefined 。编译器此时解析的话,自然找不到 seller 对象的相关数据,自然就报错了。

 

4)support 设置

如图所示,这一块的设置需要根据后端返回的数据,进行判断后动态的显示。因为有可能今天是"在线支付满28减5"排第一位,明天是"单人精彩套餐"排第一位,程序需要根据数据准确显示出第一位的内容。

问题:data.json 的 supports 数组只有type 和 description 属性,怎么才能正确取得图片呢?

设计思路:每一个 type 对应到前端中不同的 class,然后给不同的 class 添加不同的图片就可以了。所以可以给 .support 动态绑定一个class,即根据后端返回的 type(第一位),确定到底绑定哪一个 class(例如减对应的 class 为 decrease),然后将该 class 传递给动态绑定的class(:class),这样就能正确显示。

如何实现:

第一步:在组件 created() 中,定义一个 classMap,将 type 与 class 进行绑定(在 data.json 文件中,supports 数组中 type 顺序:减,折,套餐,发票,保障,则对应class名称:decrease,discount,special,invoice,guarantee);

第二步:给不同的 class 设置不同的图片;

第三步:将数组 classMap 绑定到 class 上,即 :class="classMap[seller.supports[0].type]。

如下图,html 中的 classMap 可以访问到this.classMap,进而访问到数组里的 class,每一个 class 又有真正对应的值。这样就能正确显示出图片。

检验成果:可以在data.json中将type=2的那一块数据调到顶端,重新编译刷新可以发现网页的数据发生了变化。

 

5)图标字体的使用:<i class="icon-keyboard_arrow_right"></i>

在 icon.styl 文件中可以找到类名。

 

6)浮层效果的入口:绝对定位,且定位在右下角,这个定位是相对于content-wrapper

 

7)CSS设置

7-1)在 base.styl 文件中,定义通用样式。

7-2)左右两块区域是并排,可以直接设置为 inline-block。

7-3)左右两块区域中间有空格,这是因为空白字符(换行符、空格符等)的影响,为消除该影响,可以设置 font-size 为 0。

7-4)根据不同 dpr 进行不同图片的显示,在 mixin.styl 中设置该函数。

1   bg-image($url)
2   // 正常情况下
3   background-image url($url + "@2x.png")
4   // 最小 dpr=3 的情况下
5   @madia (-webkit-min-device-pixel-ratio 3) (-min-device-pixel-ratio 3)
6     background-image url($url + "@3x.png")

7-5)不能像加载 js 库一样,进行省略别名的使用。因为那是针对 import js 库,css 这边是不可以的,所以这里只能写相对路径。

@import '../../common/stylus/mixin'

7-6)Chrome 浏览器默认最小字体是 12px,所以设置为 10px 没办法生效,可以在手机端看效果。

 

8)图片地址
在控制台可以看到,图片在编译过程被webpack自动转换成 base64 编码格式,然后导入代码了,所以这里是 base64 的地址。

 

 

 2.公告栏部分

公告栏拆分:左边图片,中间文字,右边图标字体(箭头),点击箭头可以展开浮层。

1 <div class="bulletin-wrapper" v-on:click="showDetail">
2   <span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
3   <i class="icon-keyboard_arrow_right"></i>
4 </div>

1)清除空白字符的影响

方法1:设置 font-size 为 0 。

方法2:将两个 span 写在一起,不要换行。

这里选第二个方法,因为后边要进行省略号的设置,如果选第一个方法,会看不见省略号。

 

2)省略号设置

1 /*要为文字限定显示范围,位置应该限定到箭头前面*/
2 padding 0 22px 0 12px
3 /*文字超过宽度省略的设置*/
4 white-space nowrap
5 overflow hidden
6 text-overflow ellipsis

要进行省略号设置,必须先限定文字显示范围,所以这里设置 padding 时,是设置到省略号前面,代表从省略号到最后那段距离都不能显示文字。 

后面三行代码是为了设置省略号:white-space nowrap(不自动换行)、overflow hidden(超过范围隐藏)、text-overflow ellipsis(省略号设置)。

 

3)图标字体(箭头),即浮层效果的入口:绝对定位,这个定位是相对于父元素 bulletin-wrapper 。

 

4)点击效果的实现

给元素添加点击事件 v-on:click="showDetail" ,在 method() 方法里定义点击事件。

detailShow 是弹出层设置的用于控制弹出层显示隐藏的一个变量,detailShow 值为 true 时显示,为 false 时隐藏。更多知识在弹出层进行讲解。

 

5)CSS设置

左边图片和中间文字不是居中对齐,这是因为两部分的高度不同,所以设置 vertical-align top,让两部分顶部对齐。此时发现左边图片在上方,可以设置文字的 margin-top:(28-12)/2=8px (父元素高28px,文字12px),即可居中对齐。

 

3.背景层 

  

可以看到,背景是虚化的图片。

1)给背景层设置绝对定位,这个定位是相对于父元素 header 。

2)设置 z-index 为 -1,让该背景层处于最下方,不要遮盖掉其他设置。

3)宽高撑满整个背景层。

4)使用 filter 滤镜产生模糊效果

设置完在浏览器查看时,会发现背景渗透到公告下,这是因为设置了blur。可以在父元素 header 上添加 overflow:hidden ,即可解决该问题。

 

  

三、弹出层

浮层高度大于手机时,应该有下拉功能。不管浮层中内容多少,关闭按钮都必须固定位于下方,这里将用到 css sticky fotter 布局来实现该功能。该布局一般会有 detail-wrapper 和 detail-close(关闭按钮) 两个层,而真正内容写在 detail-main 中:

1   <div class="detail-wrapper clearfix">
2     <div class="detail-main"></div>
3   </div>
4   <div class="detail-close"></div>

 下面将首先对弹出层的代码框架布局进行详细讲解,然后再把弹出层分成弹出层内容还有关闭按钮两部分进行讲解。

 

1.代码框架布局

以下是弹出层的代码框架:

 

1)显示隐藏设置(v-show

弹出层是应当实现显示和隐藏功能,这里可以使用 vue 的条件渲染 v-show 来实现。设置一个 detailShow 变量,通过改变这个变量的值(true/false),来控制弹出层显示或隐藏。

如何实现:给vue实例中添加一个选项data(),这个 data() 是一个function(这是之前说过的组件复用问题),该方法会返回一个 object,object 里面的变量就是它所需要去跟踪依赖的一些变量。也就是说,vue在实例化时,会对 data 对象中的变量去遍历,然后添加 getter 和 setter 方法,这样,当变量变化时,DOM 会根据变量的变化而变化,所以这里用v-show控制detailShow。

使用 v-show 的好处:不需要额外写 DOM 代码,都是直接操作变量即可。

 

 

PS:代码中有两处需要涉及到 detailShow 的设置,为这两个层添加 click 事件。

原理:在方法里面改变 detailShow 的值,因为 detailShow 是依赖跟踪的,当点击之后调用该方法,this.detailShow 会依赖跟踪到 data 里面的 detailShow,并改变其值,因为是依赖跟踪,vue 可以检测到其变化,并将该变化映射到 DOM 上,这样就可以通过改变数据的值来实现弹层的悬浮或者隐藏。

 

2)过渡渐变(transition

弹出层弹出的时候,背景颜色应该由浅到深,弹出层隐藏的时候,背景颜色应该由深到浅。这种渐变的设置可以用 vue 的内置组件 transition 实现。以下是基本的 HTML 结构:

transition 有一个 name 属性,添加该属性后,会自动生成 CSS 过渡类名。例如:name: 'fade' 将自动拓展为.fade-enter,.fade-enter-active等。在这里设置背景颜色后,再设置过渡的类名 .fade-enter,.fade-enter-active,.fade-leave-to,.fade-leave-active 的样式即可。

可在浏览器中查看效果:

 

3)CSS设置

3-1)弹出层相对于窗口应该是 fixed 结构。

3-2)弹出层应该位于所有层的最上方,所以父元素设置 z-index 为 100 。

3-3)当内容超过屏幕高度,弹出层应该产生滚动条,所以设置 overflow 属性。

3-4)背景文字虚化设置 backdrop-filter: blur(10px),这个设置支持 ios 设备,可以在 ios 手机上查看效果。可以查看 backdrop-filter 的使用范围。

 

2.弹出层内容

1)CSS设置

1-1)要给 .detail-wrapper 层加上清除浮动 clearfix ,样式设置写在 base.styl 。

1-2).detail-wrapper 的最小高度应该跟视口高度一样,所以设置为 100%。

1-3).detail-main 中设置 padding-bottom 64px。如果没有 padding-bottom,.detail-main 的内容会盖住关闭按钮。加了后会撑开高度,这部分高度会给按钮留住位置。

 

2)星级组件设置(star.vue)

星级评价这一块内容在之后多个页面会使用到,所以将其抽象成 star 组件。

在这里设置为总共五颗星,评分范围为 0-5,评分规则是向下取0.5倍数的值,比如4到4.4会变成4(显示四颗全星,一颗无星),4.5到4.9会变成4.5(显示四颗全星和一颗半星)。此处有一个思路,就是根据不同分数范围制作不同的图片,然后引用图片,如下图。

但是为了增加代码的可用性,最好不要这样做,我们可以通过分数来计算到底有多少颗全星和无星,以及是否有半星。

先创建相应的文件,在 star.vue 中编写代码。

实现代码如下:

2-1)HTML 代码

1 <template>
2   <div class="star" :class="starType">
3     <span v-for="itemClass in itemClasses" :class="itemClass" class="star-item"></span>
4   </div>
5 </template>

先给星级组件添加父元素,类名是 star,将星级组件包裹起来,并 v-bind 方式定义一个 starType 。再添加评星,用 v-for 指令遍历 itemClasses 数组,这个数组是最后所有星星存放的数组,遍历该数组,得到不同的 itemClass,再通过 CSS 设置不同 itemClass ,最终就可以得到不同的星级评定。

2-2)JavaScript 代码

 1 <script type="text/ecmascript-6">
 2   const LENGTH = 5;
 3   const CLS_ON = 'on';
 4   const CLS_HALF = 'half';
 5   const CLS_OFF = 'off';
 6 
 7   export default {
 8     props: {
 9       size: {
10         type: Number
11       },
12       score: {         // 评价的分数
13         type: Number
14       }
15     },
16     computed: {
17       starType() {
18         return 'star-' + this.size;
19       },
20       itemClasses() {
21         let result = []; // 最终所有星星存放的数组
22         let score = Math.floor(this.score * 2) / 2;
23         let hasDecimal = score % 1 !== 0;   // 小数
24         let integer = Math.floor(score);    // 整数
25         for (let i = 0; i < integer; i++) { // 全星
26           result.push(CLS_ON);
27         }
28         if (hasDecimal) { // 是否有半星(半星最多有一个)
29           result.push(CLS_HALF);
30         }
31         while (result.length < LENGTH) {    // 无星
32           result.push(CLS_OFF);
33         }
34         return result;
35       }
36     }
37   };
38 </script>

以下是部分 CSS 代码:

 1 .star
 2     ...
 3     &.star-48
 4       .star-item
 5         ...
 6         &.on
 7           bg-image('star48_on')
 8         &.half
 9           bg-image('star48_half')
10         &.off
11           bg-image('star48_off')
12     &.star-36
13      ...
14     &.star-24
15      ...

2-2-1)先通过 props 去接收外部传来的两个参数:size 和 score,这两个参数都是 Number 类型。size 的值是 24,36,48 中的一个,是图片的尺寸类型,score是评分。

2-2-2)starType 是通过 size 参数计算来的,可以通过 conputed 计算属性计算出来,因为参数 size 本身会有 getter 和 setter 方法依赖跟踪。通过计算属性拼接出starType 的值,从而产生不同的 starType,即不同的 class,接下来对 class(star-48,star-36,star-24)进行样式设计即可。其中,on、half、off 分别代表全星、半星、无星。

2-2-3)itemClasses 是数组,要根据 score 进行计算,数组内容其实就是on、half,off 的组合,通过这些类组成星级评定。比如三颗半星,那么 itemClasses 应该是有三个 on,一个 half,一个 off。依旧在 conputed 计算属性里写代码,因为 itemClasses 是数组,所以先定义一个 result 数组,然后进行 score 的计算转化。在转换后的 score 的基础上,对其求余,有余数代表有一颗半星(half),对 score 进行取整,该整数就是全星(on)的个数。注意:半星最多只有一个。最后就是将 on 和 half 写进 result 数组,判断 result 长度是否为 5,不为 5 的话,要补上 off。

PS:可以看到一开始设置了四个常量 LENGTH、CLS_ON、CLS_HALF、CLS_OFF,这是为了增加代码的可用性。

2-2-4)在 header.vue 中通过 components 注册 star 组件并引用。

<star :size="48" :score="seller.score"></star>
   components: {
      star
    }

 

3)小标题的设置

小标题的设置,从样式上看,应该是左右两边的线有一个自适应的能力,可根据屏幕大小延伸,标题居中,标题与线之间还有一部分空白。此处用到了一个经典布局,即 flex 布局 。下面是 HTML代码,从代码中可以看到,这里用了三个 div 元素而不是 span 元素,这是因为用 span 的话,在一些 Android 浏览器上会产生一些间距问题,因此这里用 div 。

1 <div class="title">
2   <div class="line"></div>
3   <div class="text">优惠信息</div>
4   <div class="line"></div>
5 </div>

以下是部分 CSS 代码,从代码中可以看到我们只写了一句 display flex,并没有写其它兼容性代码。这是因为编译时,vue-loader 中有 postcss 工具,可以自动添加有兼容性代码样式。postcss 是根据 can i use 官网写的代码。

 1 .title
 2   display flex
 3   width 80%
 4   margin 28px auto 24px auto
 5   .line
 6     flex 1
 7     position relative
 8     top -6px
 9     border-bottom 1px solid rgba(255, 255, 255, 0.2)
10   .text
11     padding 0 12px
12     font-weight 700
13     font-size 14px

 

PS:关于 flex 布局(只挑本节涉及到的知识点进行讲解)

1. flex-grow属性:定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。

.item {
    flex-grow: <number>; /* default 0 */
}

如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

2. flex-shrink属性:定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

.item {
    flex-shrink: <number>; /* default 1 */
}

如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。负值对该属性无效。

3. flex-basis属性:定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

.item {
    flex-basis: <length> | auto; /* default auto */
}

它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。

4.flex属性:是 flex-grow, flex-shrink 和 flex-basis 的简写,默认值为 0 1 auto。后两个属性可选。

.item {
    flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。

建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。

 

4)优惠信息的设置

使用无序列表元素 ul 和 li,用 v-for 遍历 data.json 文件中的 supports 数组,然后展示出来。因为 v-for 支持一个可选的第二个参数为当前项的索引,所以我们写大量额外的代码,只需要在 v-for 遍历时,加上 index,下边就可以可以很容易的取到正确的图片和文字了(classMap是五个图片类名的数组)。更多关于 v-for的知识,可以查看:列表渲染 以及 v-for-遍历数组时的参数顺序-变更 。

1 <ul v-if="seller.supports" class="supports">
2   <li class="support-item" v-for="(item, index) in seller.supports">
3     <span class="icon" :class="classMap[seller.supports[index].type]"></span>
4     <span class="text">{{seller.supports[index].description}}</span>
5   </li>
6 </ul>

 

3.关闭按钮

1)CSS设置

margin -64px auto 0 auto:因为 .detail-wrapper 与 .detail-close 同级,而当 .detail-wrapper 满屏时,.detail-close 是不会抢占底部的空间,所以需要设置一个向上的margin,这样才能一直出现在屏幕的最底部。

2)添加关闭按钮功能

在 HTML 代码中加上 click 事件,注意 v-on:click 可以缩写为 @click 。

1 <div class="detail-close" v-on:click="hideDetail">
2   <i class="icon-close"></i>
3 </div>

在 JavaScript 代码中将 detailShow 的值设置为 false(弹出层隐藏)。

1 methods: {
2   hideDetail() {
3     this.detailShow = false;
4   }
5 },

 

至此,header组件已全部完成。

附代码:

 1 <template>
 2   <div id="app">
 3     <v-header :seller="seller"></v-header>
 4     <div class="tab border-1px">
 5       <div class="tab-item">
 6         <!-- 使用 router-link 组件来导航. -->
 7         <!-- 通过传入 `to` 属性指定链接. -->
 8         <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
 9         <router-link to="/goods">商品</router-link>
10       </div>
11       <div class="tab-item">
12         <router-link to="/ratings">评论</router-link>
13       </div>
14       <div class="tab-item">
15         <router-link to="/seller">商家</router-link>
16       </div>
17     </div>
18     <!--内容区-->
19     <div class="content">
20       <!-- 路由出口 -->
21       <!-- 路由匹配到的组件将渲染在这里 -->
22       <router-view></router-view>
23     </div>
24   </div>
25 
26 
27 </template>
28 
29 <script type="text/ecmascript-6">
30   //  引用
31   import header from 'components/header/header.vue';
32 
33   const ERR_OK = 0;
34   //  注册
35   export default {
36     data() {
37       return {
38         seller: {}
39       };
40     },
41     created() {
42       this.$http.get('/api/seller').then(response => {
43         // get body data
44         response = response.body;
45         console.log(response);
46         if (response.errno === ERR_OK) {
47           this.seller = response.data;
48           console.log(response.data);
49         }
50       });
51     },
52     components: {
53       'v-header': header
54     }
55   };
56 </script>
57 
58 <style lang="stylus" rel="stylesheet/stylus">
59   @import "common/stylus/mixin.styl"
60   #app
61     .tab
62       display flex
63       width 100%
64       height 40px
65       line-height 40px
66       border-1px(rgba(7, 17, 27, 0.1))
67       .tab-item
68         flex 1
69         text-align center
70         /* &:表示父元素,即.tab-item */
71         & > a
72           /* 区块点击才能有反应 */
73           display block
74           font-size 14px
75           color rgb(77, 85, 93)
76           &.active
77             color rgb(240, 20, 20)
78 </style>
App.vue
  1 <template>
  2   <div class="header">
  3     <div class="content-wrapper">
  4       <div class="avatar">
  5         <img :src="seller.avatar" alt="" width="64" height="64">
  6       </div>
  7       <div class="content">
  8         <div class="title">
  9           <span class="brand"></span>
 10           <span class="name">{{seller.name}}</span>
 11         </div>
 12         <div class="description">
 13           {{seller.description}}/{{seller.deliveryTime}}分钟送达
 14         </div>
 15         <div class="support" v-if="seller.supports">
 16           <span class="icon" :class="classMap[seller.supports[0].type]"></span>
 17           <span class="text">{{seller.supports[0].description}}</span>
 18         </div>
 19       </div>
 20       <div v-if="seller.supports" class="support-count" v-on:click="showDetail">
 21         <span class="count">{{seller.supports.length}}个</span>
 22         <i class="icon-keyboard_arrow_right"></i>
 23       </div>
 24     </div>
 25     <div class="bulletin-wrapper" v-on:click="showDetail">
 26       <span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
 27       <i class="icon-keyboard_arrow_right"></i>
 28     </div>
 29     <div class="background">
 30       <img :src="seller.avatar" width="100%" height="100%">
 31     </div>
 32     <transition name="fade">
 33       <div v-show="detailShow" class="detail">
 34         <div class="detail-wrapper clearfix">
 35           <div class="detail-main">
 36             <h1 class="name">{{seller.name}}</h1>
 37             <div class="star-wrapper">
 38               <star :size="48" :score="seller.score"></star>
 39             </div>
 40             <div class="title">
 41               <div class="line"></div>
 42               <div class="text">优惠信息</div>
 43               <div class="line"></div>
 44             </div>
 45             <ul v-if="seller.supports" class="supports">
 46               <li class="support-item" v-for="(item, index) in seller.supports">
 47                 <span class="icon" :class="classMap[seller.supports[index].type]"></span>
 48                 <span class="text">{{seller.supports[index].description}}</span>
 49               </li>
 50             </ul>
 51             <div class="title">
 52               <div class="line"></div>
 53               <div class="text">商家公告</div>
 54               <div class="line"></div>
 55             </div>
 56             <div class="bulletin">
 57               <p class="content">{{seller.bulletin}}</p>
 58             </div>
 59           </div>
 60         </div>
 61         <div class="detail-close" v-on:click="hideDetail">
 62           <i class="icon-close"></i>
 63         </div>
 64       </div>
 65     </transition>
 66   </div>
 67 
 68 </template>
 69 
 70 <script type="text/ecmascript-6">
 71   import star from '../star/star.vue';
 72 
 73   export default {
 74     props: {
 75       seller: {
 76         type: Object
 77       }
 78     },
 79 //    vue的data必须用function,因为可能有多个组件实例,组件又可以复用,为了相互之间不要互相影响,必须设置data为function
 80 //    之前有过关于getter和setter知识点的讲解
 81 //    vue在new一个是实例是,解析到data函数,会给其中的参数加上getter和setter方法去监听它的变化
 82     data() {
 83       return {
 84         detailShow: false
 85       };
 86     },
 87     methods: {
 88       showDetail() {
 89         this.detailShow = true;
 90       },
 91       hideDetail() {
 92         this.detailShow = false;
 93       }
 94     },
 95     created() {
 96       this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
 97     },
 98     components: {
 99       star
100     }
101   };
102 </script>
103 
104 <style lang="stylus" rel="stylesheet/stylus">
105   @import '../../common/stylus/mixin'
106 
107   .header
108     position relative
109     /*overflow hidden*/
110     color #fff
111     background rgba(7, 17, 27, 0.5)
112     .content-wrapper
113       position relative
114       padding 24px 12px 18px 24px
115       /* 消除空白字符的影响 */
116       font-size 0 /*background-color rgba(7, 17, 27, 0.5)
117       blur 5px
118       */
119       .avatar
120         /* 左右两个区域并排显示 */
121         display inline-block
122         vertical-align top
123         img
124           border-radius 2px
125       .content
126         display inline-block
127         margin-left 16px
128         .title
129           margin 2px 0 8px 0
130           .brand
131             /* 因为是 span ,必须设置为块级元素才能指定宽高 */
132             display inline-block
133             /* 因为两个inline-block是不一样搞得,需要设置对齐方式 */
134             vertical-align top
135             width 30px
136             height 18px
137             bg-image('brand')
138             background-size 30px 18px
139             background-repeat no-repeat
140           .name
141             margin-left 6px
142             font-size 16px
143             line-height 18px
144             font-weight bold
145         .description
146           margin-bottom 10px
147           line-height 12px
148           font-size 12px
149         .support
150           margin 10px 0 2px 0
151           .icon
152             display inline-block
153             vertical-align top
154             width 12px
155             height 12px
156             margin-right 4px
157             background-size 12px 12px
158             background-repeat no-repeat
159             &.decrease
160               bg-image('decrease_1')
161             &.discount
162               bg-image('discount_1')
163             &.guarantee
164               bg-image('guarantee_1')
165             &.invoice
166               bg-image('invoice_1')
167             &.special
168               bg-image('special_1')
169           .text
170             line-height 12px
171             font-size 10px
172       .support-count
173         position absolute
174         right 12px
175         bottom 14px
176         padding 0 8px
177         height 24px
178         line-height 24px
179         border-radius 14px
180         background rgba(0, 0, 0, 0.2)
181         text-align center
182         .count
183           /*字体和图标字体是不一样的大小,所以设置vertical-align top进行顶部对齐*/
184           vertical-align top
185           font-size 10px
186         .icon-keyboard_arrow_right
187           margin-left 2px
188           /*因为在base.styl中设置了line-height:1,所以这里要设置line-height,否则无法垂直居中*/
189           line-height 24px
190           font-size 10px
191     .bulletin-wrapper
192       position relative
193       height 28px
194       line-height 28px
195       /*要为文字限定显示范围,位置应该限定到箭头前面*/
196       padding 0 22px 0 12px
197       /*文字超过宽度省略的设置*/
198       white-space nowrap
199       overflow hidden
200       text-overflow ellipsis
201       background rgba(7, 17, 27, 0.2)
202       .bulletin-title
203         display inline-block
204         vertical-align top
205         margin-top 8px
206         width 22px
207         height 12px
208         bg-image('bulletin')
209         background-size 22px 12px
210         background-repeat no-repeat
211       .bulletin-text
212         vertical-align top
213         margin 0 4px
214         text-align center
215         font-size 10px
216       .icon-keyboard_arrow_right
217         position absolute
218         /*因为在base.styl中设置了line-height:1,所以这里要设置line-height,否则无法垂直居中*/
219         font-size 10px
220         right 12px
221         bottom 8px
222     .background
223       position absolute
224       top 0
225       left 0
226       width 100%
227       height 100%
228       z-index -1
229       /*使用filter滤镜产生模糊效果*/
230       filter blur(10px)
231     .detail
232       position fixed
233       z-index 100
234       top: 0
235       left 0
236       width 100%
237       height 100%
238       /*当内容超过屏幕高度,可以产生滚动条*/
239       overflow auto
240       background-color: rgba(7, 17, 27, 0.8)
241       backdrop-filter: blur(10px)
242       /*从enter到transition,最终显示效果为transition*/
243       &.fade-enter-active, &.fade-leave-active
244         transition opacity 0.5s
245       &.fade-enter, &.fade-leave-to
246         opacity 0
247       .detail-wrapper
248         /*要定义宽度,撑开空间,下面一些居中设置才能实现*/
249         width 100%
250         /*铺满整个屏幕*/
251         min-height 100%
252         .detail-main
253           margin-top 64px
254           /*下边距包括两部分:一部分是边距,一部风是关闭按钮的高度*/
255           padding-bottom 64px
256           .name
257             line-height 16px
258             text-align center
259             font-size 16px
260             font-weight 700
261           .star-wrapper
262             margin-top 18px
263             padding 2px 0
264             text-align center
265           .title
266             display flex
267             width 80%
268             margin 28px auto 24px auto
269             .line
270               flex 1
271               position relative
272               top -6px
273               border-bottom 1px solid rgba(255, 255, 255, 0.2)
274             .text
275               padding 0 12px
276               font-weight 700
277               font-size 14px
278           .supports
279             width 80%
280             margin 0 auto
281             .support-item
282               padding 0 12px
283               margin-bottom 12px
284               font-size 0
285               &:last-child
286                 margin-bottom 0
287               .icon
288                 display inline-block
289                 vertical-align top
290                 width 16px
291                 height 16px
292                 margin-right 6px
293                 background-size 16px 16px
294                 background-repeat no-repeat
295                 &.decrease
296                   bg-image('decrease_2')
297                 &.discount
298                   bg-image('discount_2')
299                 &.guarantee
300                   bg-image('guarantee_2')
301                 &.invoice
302                   bg-image('invoice_2')
303                 &.special
304                   bg-image('special_2')
305               .text
306                 /*与图片高度一样,才能垂直居中*/
307                 line-height 16px
308                 font-size 12px
309           .bulletin
310             width 80%
311             margin 0 auto
312             .content
313               padding 0 12px
314               line-height 24px
315               font-size 12px
316       .detail-close
317         position relative
318         width 32px
319         height 32px
320         margin -64px auto 0 auto
321         clear both
322         font-size 32px
323 </style>
header.vue
 1 <template>
 2   <div class="star" :class="starType">
 3     <span v-for="itemClass in itemClasses" :class="itemClass" class="star-item"></span>
 4   </div>
 5 </template>
 6 
 7 <script type="text/ecmascript-6">
 8   const LENGTH = 5;
 9   const CLS_ON = 'on';
10   const CLS_HALF = 'half';
11   const CLS_OFF = 'off';
12 
13   export default {
14     props: {
15       size: {
16         type: Number
17       },
18       score: {         // 评价的分数
19         type: Number
20       }
21     },
22     computed: {
23       starType() {
24         return 'star-' + this.size;
25       },
26       itemClasses() {
27         let result = []; // 最终所有星星存放的数组
28         let score = Math.floor(this.score * 2) / 2;
29         let hasDecimal = score % 1 !== 0;   // 小数
30         let integer = Math.floor(score);    // 整数
31         for (let i = 0; i < integer; i++) { // 全星
32           result.push(CLS_ON);
33         }
34         if (hasDecimal) { // 是否有半星(半星最多有一个)
35           result.push(CLS_HALF);
36         }
37         while (result.length < LENGTH) {    // 无星
38           result.push(CLS_OFF);
39         }
40         return result;
41       }
42     }
43   };
44 </script>
45 
46 <style lang="stylus" rel="stylesheet/stylus">
47   @import '../../common/stylus/mixin.styl';
48 
49   .star
50     /*清除空白字符影响*/
51     font-size 0
52     .star-item
53       /*星星是横向排列*/
54       display inline-block
55       background-repeat no-repeat
56     &.star-48
57       .star-item
58         width: 20px
59         height: 20px
60         margin-right: 22px
61         background-size: 20px 20px
62         &:last-child
63           margin-right: 0
64         &.on
65           bg-image('star48_on')
66         &.half
67           bg-image('star48_half')
68         &.off
69           bg-image('star48_off')
70     &.star-36
71       .star-item
72         width: 15px
73         height: 15px
74         margin-right: 6px
75         background-size: 15px 15px
76         &:last-child
77           margin-right: 0
78         &.on
79           bg-image('star36_on')
80         &.half
81           bg-image('star36_half')
82         &.off
83           bg-image('star36_off')
84     &.star-24
85       .star-item
86         width: 10px
87         height: 10px
88         margin-right: 3px
89         background-size: 10px 10px
90         &:last-child
91           margin-right: 0
92         &.on
93           bg-image('star24_on')
94         &.half
95           bg-image('star24_half')
96         &.off
97           bg-image('star24_off')
98 </style>
star.vue
 1 body, html
 2   margin 0
 3   padding 0
 4   line-height 1
 5   font-weight 200
 6   // 字体查找顺序,从前到后,如果所有设置字体都不认识,则返回系统字体
 7   font-family 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', Arial, sans-serif
 8 
 9 ul
10   list-style none
11   padding-left 0
12   margin 0
13 
14 li
15   list-style none
16 
17 h1
18   margin 0
19 
20 .clearfix
21   display inline-block
22   &:after
23     display block
24     content "."
25     height 0
26     line-height 0
27     visibility hidden
28     clear both
29 
30 //为什么要写 -webkit :因为这里没有兼容性文件控制,所以要手写兼容性代码
31 //本例需要的是1像素的边框,所以当dpr为1.5时,取0.7倍(1.5*0.7=1.05约等于1)。dpr为2时,取0.5倍(2*0.5=1)
32 @madia (-webkit-min-device-pixel-ratio 1.5)
33 (-min-device-pixel-ratio 1.5)
34 .border-1px
35   &::after
36     -webkit-transform scaleY(0.7)
37     transform scaleY(0.7)
38 
39 @madia (-webkit-min-device-pixel-ratio 2)
40 (-min-device-pixel-ratio 2)
41 .border-1px
42   &::after
43     -webkit-transform scaleY(0.5)
44     transform scaleY(0.5)
base.styl
 1 border-1px($color)
 2   position relative
 3   // 进行伪类设置,一定要设置宽度,因为元素脱离了文档流
 4   &:after
 5     display block
 6     position absolute
 7     left 0
 8     bottom 0
 9     width 100%
10     border-top 1px solid $color
11     content ' '
12 
13 border-none()
14   &:after
15     display none
16 
17 bg-image($url)
18   // 正常情况下
19   background-image url($url + "@2x.png")
20   // 最小 dpr=3 的情况下
21   @madia (-webkit-min-device-pixel-ratio 3) (-min-device-pixel-ratio 3)
22     background-image url($url + "@3x.png")
mixin.styl

 

 



posted @ 2018-02-16 16:16  Emily恩  阅读(1556)  评论(0编辑  收藏  举报