边做边学入门微信小程序之仿豆瓣评分

微信小程序由于适用性强、逻辑简要、开发迅速的特性,叠加具有海量活跃用户的腾讯公司背景,逐渐成为了轻量级单一功能应用场景的较佳承载方式,诸如电影购票、外卖点餐、移动商城、生活服务等场景服务提供商迅速切入了。

为了贴合实际的应用情况,本篇以豆瓣评分小程序为参考样例,边做边学小程序的入门开发知识。

目录

效果图
Demo源码
开发环境
了解官方样例
    开发者工具
    样例源码结构
开发实战
    底部Tab卡页
    分析开发需求
    评分条模板
        rpx长度单位
        数据绑定
        条件渲染
        列表渲染
    电影海报模板
        模板的使用
    区块模板
    主页
        网络请求
        逻辑实现
        setData
        点击事件
    详情页
    更多页
扩展知识
思考题

效果图

先看一下对比效果图,共三个页面,分别为首页、更多页和详情页,左侧为豆瓣评分官方小程序,右侧为仿作。因API数据问题,没有做搜索功能。

主页

img

更多页

img

详情页

img

Demo源码

本篇完整源码已提交在: https://github.com/cnwen/wechatapp_movie

开发环境

调试基础库:1.9.91(2018.03.07)

微信开发者工具:Windows版v1.02.1802270

了解官方样例

打开微信开发者工具,新建一个小程序项目。

1.选择项目代码存放的目录;

2.填入你的小程序AppID(若无AppID请点击“注册”获取,也可选择“体验小程序”,如需真机预览须有AppID);

3.勾选“建立普通快速启动模板”。

点击“确定”按钮后,开发者工具将为我们建立一个简单的小程序模板,我们可以通过这个样例来建立对开发者工具和小程序的初步认识。

img

开发者工具

我们观察开发者工具,发现由三个主要区域构成,分别是模拟器、编辑器和调试器。

img

模拟器:顶端含有三个下拉列表,可以配置模拟的机型和所处的网络环境。

编辑器:分为源码目录区域和代码区域。

调试器:顶端含有控制台、网络、存储等选项卡页。

样例源码结构

根目录含有pages、utils文件夹和三个名称为app的文件。顾名思义,pages正是存放小程序各个页面的文件夹,一些公共的工具类建议放在utils文件夹,app文件是小程序的全局文件。

点击查看app.json文件的源码,可以看到含有pages和window两个键值对。

img

pages负责配置小程序的页面,里面有2条路径,分别对应index和logs页面。

Tips:1.第一条路径固定为小程序的首页,如果把logs路径放到首位,那么logs页面是首页;2.同一个页面的js/wxml/wxss文件的名称必须相同,因为路径是以文件名称来识别的,路径是“pages/index/index”,注意后面不带js/wxml/wxss等后缀,系统会在该路径寻找需要格式文件;3.需要显示的独立页面都需要在此处配置,template模板文件则不需要。

window负责全局的窗口配置,如导航栏背景色、导航栏文字等。你可以修改它们的值,保存后在模拟器上看到效果。

试试将navigationBarBackgroundColor的值改为#ffae00,将navigationBarTitleText的值改为“电影排行榜”,按Ctrl+S键保存看看模拟器中的效果吧。

Tips:1.backgroundTextStyle的值目前只有两种:light和dark;2.navigationBarTextStyle的值目前只有两种:black和white。

回到源码目录,对比index和logs页面的构成,发现index页面并没有.json文件,可见这个文件并非是必须的。但是,如果有这个文件,那么必然不能为空,否则控制台会报错,可在里面写入一个大括号{}保存即可。

开发实战

官方样例先认识到这里,我们对开发者工具和小程序源码构成有了一个初步的印象后,开始边做边学。

底部Tab卡页

我们使用鼠标右键将源码目录pages下的index/logs两个文件夹删除,并打开app.json,在pages的值中配置下图中的两条路径。

img

按Ctrl+S键保存,开发者工具将自动在指定路径为我们创建两个页面,如下图所示。

img

此时,模拟器中的首页已经变成了movies目录下的index页面,因为我们刚才把这个页面的路径配置在app.json文件中pages值的首位了。

img

如果我们要查看mine页面怎么办呢?除了使用后文将提到的页面跳转功能,这里用Tab卡页的切换功能来试试。在app.json文件中添加tabBar内容,如下所示。

{
  "pages":[
    "pages/movies/index",
    "pages/mine/mine"
  ],
  "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#ffae00",
    "navigationBarTitleText": "电影排行榜",
    "navigationBarTextStyle":"white"
  },
  "tabBar": {
    "list": [
      {
        "pagePath": "pages/movies/index",
        "text": "电影"
      },
      {
        "pagePath": "pages/mine/mine",
        "text": "我的"
      }
    ],
    "borderStyle": "black",
    "selectedColor": "#ffae00"
  }
}

模拟器预览效果如下图:

img

在list的值中,我们配置了2个卡页,text是卡页的文字,pathPath代表了页面路径,当点击卡页时,将跳转到指定路径的页面。注意卡页数必须为2-5个才合规,否则控制台会报错。并且,这里的路径必须被包含在顶端的pages值中,因为所有可抵达页面都必须在pages中配置路径。

红色箭头指向处有一条水平灰色线,这是卡页和内容页的分界线,由borderStyle属性控制,其值默认为black,目前暂时只有black和white值可选。

selectedColor表示卡页选中时,其文字的颜色,用十六进制表示。另外卡页背景色也可配置。

选项卡可以含有图标,配置方法如下:

{
    "pagePath": "pages/movies/index",
    "text": "电影",
    "iconPath": "images/tabbar/movie.png",
    "selectedIconPath": "images/tabbar/movieSelected.png"
}

img
img

一个为默认图标,一个是选中时显示的图标。(请看图标路径,我在左侧目录树的根目录新建了images/tabbar目录,并放入了4张图标,图标资源在Github源码中有)

这样,首页Tab便完成了。接下来我们来分析页面结构,并实现一些公用的template模板文件。

分析开发需求

作为编码者,在开始编码之前,我们要养成先从整体层面上分析整体需求的好习惯,有利于后续的代码编写及维护。

img

观察首页、更多页和电影详情页,可以很容易地归纳出一些可以共用的页面元素:1.首页由三大块组成,正在热映、即将上映和排行榜区块除了数据不同,页面结构是相同的(即红色框,后文我用block区块模板代指这块);2.每个block模板中含有若干个海报模板,由电影海报、电影名称和评分条、评分数组成(蓝色框,后文称poster海报模板);3.每个评分条是由5颗星星组成的(粉红框,后文称ratingbar评分条模板)。

评分条模板

模板页面(template)是官方提供的一种实现页面元素复用、减少重复工作的良好实现形式。

从上面的分析中,我们发现这里提到的三个模板是层层嵌套的,因此,作为入门学习者,我们先从最内层的模板开始实现,即先实现评分条ratingbar模板文件。

img

评分星星有三种状态,我们在images目录中新建ratingbar目录,并将这三个星星图标放入。(Github源码中含有本文所需的所有素材)

img

在movies目录右键鼠标,依次新建名为ratingbar的目录和Page。打开app.json文件,我们发现开发者工具自动在pages值为我们配置了ratingbar页面的路径。

img

作为模板文件,是在其它页面中导入使用的,它不会单独被使用,因此其实它不需要在app.json的pages值中配置路径,并且该页面只需wxml和wxss即可,js和json文件都是用不上的,它所含的页面元素的数据和行为都是由引用它的页面来操纵的,后文会对此有所涉及。

但在此时,我们需要在模拟器中调试、查看其效果,所以要将它当作单独页面来使用一次,调试完后可将js/json文件删除,并删除在app.json中pages值中的路径,此是后话,暂且略过。

我们在app.json中将ratingbar页面路径移到pages值的首位(见上图),保存后发现模拟器的首页变成了ratingbar页面。

img

页面上唯一的一条内容是由开发者工具在新建页面时自动生成的。我们打开ratingbar.wxml页面,将自动生成的标签内容删除,并输入以下内容,按Shift+Alt+F键格式化代码:

<!--pages/movies/ratingbar/ratingbar.wxml-->
<view class='ratingbar-stars'>
  <!--全黄星星-->
  <image src='/images/ratingbar/star_fill_whole.png'/>
  <image src='/images/ratingbar/star_fill_whole.png'/>
  <image src='../../../images/ratingbar/star_fill_whole.png'/>
  <!--半黄半灰星星-->
  <image src='/images/ratingbar/star_fill_half.png'/> 
  <!--全灰星星-->
  <image src='../../../images/ratingbar/star_fill_none.png'/>
</view> 

可以视为官方提供的一种容器,需要成对出现的,在这里它内部含有5个image组件。class表示其样式,它将会去同页面的wxss文件中查找并渲染值为.ratingbar-stars的样式(见下文)。image是图像组件,有一个属性src,表示要显示的图片的地址。像这类内容不需要再包含其它控件的控件,你也可以写成单闭合标签的形式。

这里src的路径出现了2种写法,绝对路径和相对路径,如果不了解的可另外拓展一下,这里的路径是一样的。

再点开ratingbar.wxss文件,并输入以下内容:

/* pages/movies/ratingbar/ratingbar.wxss */
/* class="ratingbar-stars"的控件的样式 */
.ratingbar-stars {
  display: flex;
  flex-direction: row;
  padding-right: 4rpx;
}

/* class="ratingbar-stars"的控件下所包含的image控件的样式 */
.ratingbar-stars image {
  height: 17rpx;
  width: 17rpx;
  padding-top: 5rpx;
  padding-right: 4rpx;
}

这里的首个.ratingbar-stars样式(请注意样式名前面有个小点号“.”),与的class相呼应,将会用于渲染该控件。

这里采用了CSS中著名的flex弹性盒子模型,flex-direction:row;意味着其内部控件将采用水平横向的方式排列,要表示竖直排列可将值改为column。

第二个.ratingbar-stars image样式(请注意中间有空格,且image前面没有点号),表示渲染class='ratingbar-stars'的控件内部包含的image控件,这里表示了高height、宽width、距离顶部的内边距padding-top、距离右侧的内边距padding-right。CSS的更多属性知识欢迎另行拓展,这是一个多记多练才能生巧的知识。

rpx长度单位

rpx是微信小程序推出的单位,可以根据不同手机不同的屏幕宽度进行内容自适应,使页面元素在不同屏幕宽度的手机上看起来具有一致性。

无论手机屏幕实际宽度是多少,小程序都会在底层将屏幕宽度换算成750份,如果设计师以iphone6的750*1334(物理像素)为标准出设计稿的话,1rpx=0.5px=1物理像素,你可以直接使用设计师标注的参数加上rpx即可。

上面的width:17rpx表示无论在什么样的移动设备上,其大小都为750份中的17份,宽屏则显示大一些,窄屏则显示小一些,视觉效果一致。

按Ctrl+S键保存现有代码,模拟器即时显示出了目前的页面效果:3颗黄星、1颗半星、1颗灰星。

img

在wxml文件中,我们在内写了5个静态的image图像控件。然而在实际场景中,这个不可能是写死的,而应该是根据电影的不同评分进行相应的显示。

动态数据涉及到一些数据绑定等相关的知识,这里先简要介绍一下。

数据绑定

{{}}

WXML 中的动态数据均来自对应 Page 的 data,数据绑定使用 Mustache 语法(双大括号)将变量包起来,如:

<view wx:for="{{count}}">
  <text>{{stars}}</text>
</view>

页面渲染时,系统将去对应页面的js文件的data属性中寻找count和stars变量。

条件渲染

wx:if

在框架中,使用 wx:if="{{条件语句}}" 来判断是否需要渲染该代码块:

<image wx:if="{{stars>30}}" src='/images/ratingbar/star_fill_whole.png' />

上条语句表示当满足stars>30的条件时,渲染image控件。

除了if条件外,还有wx:elif 和 wx:else语句:

<image wx:if="{{stars>30}}" src='/images/ratingbar/star_fill_whole.png' />
<image wx:elif="{{stars>20}}" src='/images/ratingbar/star_fill_half.png' />
<image wx:else src='/images/ratingbar/star_fill_none.png' />

wx:if 有且只有一个;

wx:elif 是else if的意思,可以有多个;

wx:else 最多只有一个。

系统按顺序判断各个条件,遇到成立的条件时则渲染该控件,其余控件则不会渲染。

注意:内部的条件语句的结果为{{false}}才表示条件不成立,不带{{}}的值将会被视为文本从而判定为条件成立。

<view wx:if="{{false}}"/>   <!--条件不成立-->
<view wx:if="false"/>  <!--条件成立-->
<view wx:if="3"/>  <!--条件成立-->
列表渲染

wx:for

在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。数组长度为多少,就会重复渲染多少次。

<block wx:for="{{count}}">
    <text>{{index}}:{{item}}</text>
</block>

在循环时,当前项的变量名默认为 item,其下标变量名默认为 index。

<block wx:for="{{count1}}">
  <block wx:for="{{count2}}">
    <text>{{index}}:{{item}}</text>
  </block>
</block>

如果遇到这种多重循环呢,如何在内部表示不同数组的当前项和下标呢?

可以用wx:for-item和wx:for-index分别指定当前项的变量名和下标的变量名:

<block wx:for="{{count1}}" wx:for-item="outer" wx:for-index="i">
  <block wx:for="{{count2}}" wx:for-item="inner" wx:for-index="j">
    <text>{{i}}:{{outer}}</text>
    <text>{{j}}:{{inner}}</text>
  </block>
</block>

数据绑定相关的知识大致了解了,我们打开ratingbar.js,并在data内定义两个变量:

// pages/movies/ratingbar/ratingbar.js
Page({
  data: {
    count:[3,5,6,8,9],
    stars:16
  }
})

然后将ratingbar.wxml文件内容修改为:

<!--pages/movies/ratingbar/ratingbar.wxml-->
<view class='ratingbar-stars'>
  <!--count数组长度为5,共5次循环显示5颗星星-->
  <block wx:for="{{count}}">
    <!--全黄星星-->
    <image wx:if="{{stars/10>=index+1}}" src='/images/ratingbar/star_fill_whole.png' />
    <!--半黄半灰星星-->
    <image wx:elif="{{stars/10>=index && stars%10!=0}}" src='/images/ratingbar/star_fill_half.png' />
    <!--全灰星星-->
    <image wx:else src='/images/ratingbar/star_fill_none.png' />
  </block>
</view>

按Ctrl+S保存看看模拟器的效果,把stars的值改为其它数(0-50之间)试试看星星的显示效果吧。Tips:这里的条件语句是研究豆瓣电影API里电影评分的规律得来的。它的stars取值为0/5/10……40/45/50。

据我们之前对豆瓣评分小程序的分析,评分条一般作为一个元素在相关页面中显示,不会独立作为页面来显示。小程序官方提供了机制来解决这种复用性的问题,代码片段一处编写,多处使用,极大地精简了代码的臃肿,也令程序员有更多精力专注于必要的地方。其结构如下:

<template name="模板名称">
  <!--这里是要复用的代码片段-->
</template>

我们将刚才编写的ratingbar.wxml代码最外层加上模板标识:

<!--pages/movies/ratingbar/ratingbar.wxml-->
<template name="template-ratingbar-stars">
  <view class='ratingbar-stars'>
    <!--数组长度为5,共5次循环显示5颗星星-->
    <block wx:for="{{[3,5,6,8,9]}}">
      <!--全黄星星-->
      <image wx:if="{{stars/10>=index+1}}" src='/images/ratingbar/star_fill_whole.png' />
      <!--半黄半灰星星-->
      <image wx:elif="{{stars/10>=index && stars%10!=0}}" src='/images/ratingbar/star_fill_half.png' />
      <!--全灰星星-->
      <image wx:else src='/images/ratingbar/star_fill_none.png' />
    </block>
  </view>
</template>

模板名称是template-ratingbar-stars,在其它页面引入本模板后,根据该名称即可找到此模板。因为评分条有且只有5颗星星,所以这里将count的值直接写在此处wx:for="{{[3,5,6,8,9]}}",然后我们把ratingbar.js中的相关值注释掉:

Page({
  data: {
    // count:[3,5,6,8,9],
    // stars:16
  }
})

因为作为模板,将在多处调用,其所使用的值(如这里的stars)将由调用的地方传入,也正因为这样,才有复用性可言。其实,模板文件只有wxml和wxss有用,js和json文件删除亦可。

电影海报模板

接下来,我们来编写电影海报,就是蓝框这个:

img
有多处使用了这样的结构,显然,也应当是一个模板文件。

我们可以参照编写评分条模板的步骤,先将其当作为一个页面,配置在小程序的首页,在js中模拟数据,调试成功后,再改成模板页面。

首先,在movies目录下新建poster目录和poster页面,在app.json的pages属性中,将poster页面路径放在首位(ratingbar路径可以删除),以便在模拟器中查看效果和调试。

{
  "pages": [
    "pages/movies/poster/poster",
    "pages/movies/ratingbar/ratingbar",
    "pages/movies/index",
    "pages/mine/mine"
  ],
   ……
}

在poster.js中写入一些模拟数据:

// pages/movies/poster/poster.js
Page({
  data: {
    title: "奇迹男孩 Wonder",
    images: {
      large: "https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2507709428.jpg"
    },
    rating: {
      average: 8.6,
      max: 10,
      min: 0,
      stars: "45"
    }
  }
})

我们将在页面中通过绑定数据来获取这些数据,如电影名称{{title}}、海报图片{{images.large}}、电影评分{{rating.average}}等。

在poster.wxss中写入:

/* 导入评分条模板wxss文件,注意是@import */
@import "../ratingbar/ratingbar.wxss";
.movie {
  display: flex;
  flex-direction: column;
  padding-right: 12rpx;  
}
.poster {
  width: 200rpx;
  height: 270rpx;
  padding-bottom: 10rpx;
}
.movie-name {
  color: #333333;
  font-size: 24rpx;
  line-height: 24rpx;
  margin: 10rpx 0 5rpx 0;
}
.ratingbar {
  display: flex;
  flex-direction: row;
}
.ratingbar-score{
  color: #999999;
  font-size: 20rpx;
}
模板的使用

这里用到了评分条模板,因此在开头导入了评分条模板的wxss样式。每个.样式都对应着下文相关控件的class。

在poster.wxml中写入:

<!--导入评分条模板wxml文件,注意别少了后面的 / 符号-->
<import src="../ratingbar/ratingbar.wxml" />

<view class='movie' catchtap='catchTapMovie' data-movieid='{{id}}'>
  <!--海报图-->
  <image class="poster" src='{{images.large}}'></image>
  <!--电影名称-->
  <text class='movie-name'>{{title}}</text>
  <!--评分星星和数字-->
  <view class='ratingbar'>
    <!--评分条-->
    <template is="template-ratingbar-stars" data="{{...rating}}" />
    <!--评分分数-->
    <text class='ratingbar-score'>{{rating.average}}</text>
  </view>
</view>

同理,开头也导入了评分条模板的wxml文件,并通过以下方式使用。

<!--评分条-->
<template is="template-ratingbar-stars" data="{{...rating}}" />

is属性值正是评分条模板的name名称,data值将相关数据传入评分条模板。

poster.js的data属性中含有rating数据,其格式见下文。上文中的...rating即是将rating数据散开,将其内容传入评分条模板,评分条模板里可以直接使用{{stars}},而不需要通过{{rating.stars}}的方式。

rating: {
      average: 8.6,
      max: 10,
      min: 0,
      stars: "45"
    }

这里还出现了诸如下文的属性名,我们将留到后面讲解。

<view class='movie' catchtap='catchTapMovie' data-movieid='{{id}}'>

现在,将poster.js的data属性中的模拟数据注释或删除掉,并将poster.wxml封装成模版,如下文所示。

<!--导入评分条模板wxml文件,注意别少了后面的 / 符号-->
<import src="../ratingbar/ratingbar.wxml" />

<!--封装成名称为template-poster的模板-->
<template name="template-poster">
  <view class='movie' catchtap='catchTapMovie' data-movieid='{{id}}'>
    <!--海报图-->
    <image class="poster" src='{{images.large}}'></image>
    <!--电影名称-->
    <text class='movie-name'>{{title}}</text>
    <!--评分星星和数字-->
    <view class='ratingbar'>
      <!--评分条-->
      <template is="template-ratingbar-stars" data="{{...rating}}" />
      <!--评分分数-->
      <text class='ratingbar-score'>{{rating.average}}</text>
    </view>
  </view>
</template>

区块模板

有了电影海报模板后,接下来我们进行下一步。观察到首页是由三个结构一模一样的红色区块组成,显然,这也可以是一个模板,暂且称之为block吧。

img

步骤和先前个模板一样,为节省篇幅,这里会更简略地以贴代码为主。首页在movies目录下新建block目录和页面,并在app.json文件的pages中将block页面的路径移到首位,以便观察模拟器效果。

在images目录新建block目录,放入下面这张名为arrow-right.png的右箭头图片。

img

在block.js文件的data属性中写入模拟的数据(如下),有两个键值对,一为blockTitle区块标题,一为blockMovies为区块电影数据(与豆瓣API返回的格式一致,有删减字段,但结构不变)。

// pages/movies/block/block.js

Page({
  data: {
    blockTitle:"正在热映",
    blockMovies: {
      "count": 4,
      "start": 0,
      "subjects": [{
        "casts": [{
          "avatars": {
            "large": "https://img1.doubanio.com/view/celebrity/s_ratio_celebrity/public/p1509423054.09.jpg"
          },
          "name": "阿德瓦·香登"
        },
        {
          "avatars": {
            "large": "https://img1.doubanio.com/view/celebrity/s_ratio_celebrity/public/p13628.jpg"
          },
          "name": "阿米尔·汗"
        },
        {
          "avatars": {
            "large": "https://img3.doubanio.com/view/celebrity/s_ratio_celebrity/public/p1494080264.12.jpg"
          },
          "name": "塞伊拉·沃西"
        }
        ],
        "comments_count": 5951,
        "countries": [
          "印度"
        ],
        "directors": [{
          "avatars": {
            "large": null
          },
          "name": "阿德瓦·香登"
        }],
        "genres": [
          "剧情",
          "音乐"
        ],
        "id": 259,
        "images": {
          "large": "https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2508925590.jpg"
        },
        "original_title": "神秘巨星 Secret Superstar",
        "rating": {
          "average": 8.2,
          "max": 10,
          "min": 0,
          "stars": "40"
        },
        "reviews_count": 292,
        "summary": "14岁的印度少女尹希娅(塞伊拉·沃西 饰)热爱唱歌,因父亲阻挠,她只能蒙面拍摄并上传自弹自唱原创歌曲的视频,孰料凭借天籁歌喉在网上一炮而红,备受争议的音乐人夏克提·库马尔(阿米尔·汗 饰)也向她抛出橄榄枝,尹希娅的生活发生了翻天覆地的变化……",
        "title": "神秘巨星 Secret Superstar",
        "warning": "数据来源于网络整理,仅供学习,禁止他用。如有侵权请联系公众号:小楼昨夜又秋风。我将及时删除。",
        "wish_count": 22447,
        "year": 2017
      },
      {
        "casts": [{
          "avatars": {
            "large": "https://img3.doubanio.com/view/celebrity/s_ratio_celebrity/public/p19485.jpg"
          },
          "name": "李芳芳"
        },
        {
          "avatars": {
            "large": "https://img3.doubanio.com/view/celebrity/s_ratio_celebrity/public/p1359895311.0.jpg"
          },
          "name": "章子怡"
        },
        {
          "avatars": {
            "large": "https://img3.doubanio.com/view/celebrity/s_ratio_celebrity/public/p1472787652.32.jpg"
          },
          "name": "黄晓明"
        }
        ],
        "comments_count": 58027,
        "countries": [
          "中国大陆"
        ],
        "directors": [{
          "avatars": {
            "large": null
          },
          "name": "李芳芳"
        }],
        "genres": [
          "剧情",
          "爱情",
          "战争"
        ],
        "id": 265,
        "images": {
          "large": "https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2507572275.jpg"
        },
        "original_title": "无问西东",
        "rating": {
          "average": 7.5,
          "max": 10,
          "min": 0,
          "stars": "40"
        },
        "reviews_count": 4354,
        "summary": "如果提前了解了你所要面对的人生,你是否还会有勇气前来?吴岭澜、沈光耀、王敏佳、陈鹏、张果果,几个年轻人满怀诸多渴望,在四个非同凡响的时空中一路前行。\\n吴岭澜(陈楚生 饰),出发时意气风发,却很快在途中迷失了方向。沈光耀(王力宏 饰),自愿参与了最残酷的战争,他一直在努力去做那些令他害怕,但重要的事。王敏佳(章子怡 饰)最初的错误,只是为了虚荣撒了一个小谎;最初的烦恼,只是在两个优秀的男人中选择一个。但命运,却把她拖入被众人唾骂的深渊。陈鹏(黄晓明 饰)把爱情摆在了理想前面,但爱情却没有把他摆在前面。他说,“我有人要照顾”,纵然这意味着与所有人作对,意味着要和她一起被放逐千里。张果果(张震 饰),身处尔虞我诈的职场,“赢”是他的习惯。为了赢,他总是见招拆招,先发制人。而有一天,他却面临了一个比“赢”更重要的选择。这几个年轻人,在最好的年纪迎来了最残酷的...",
        "title": "无问西东",
        "warning": "数据来源于网络整理,仅供学习,禁止他用。如有侵权请联系公众号:小楼昨夜又秋风。我将及时删除。",
        "wish_count": 32890,
        "year": 2018
      },
      {
        "casts": [{
          "avatars": {
            "large": "https://img3.doubanio.com/view/celebrity/s_ratio_celebrity/public/p57551.jpg"
          },
          "name": "斯蒂芬·卓博斯基"
        },
        {
          "avatars": {
            "large": "https://img1.doubanio.com/view/celebrity/s_ratio_celebrity/public/p1456737567.18.jpg"
          },
          "name": "雅各布·特伦布莱"
        },
        {
          "avatars": {
            "large": "https://img1.doubanio.com/view/celebrity/s_ratio_celebrity/public/p8889.jpg"
          },
          "name": "朱莉娅·罗伯茨"
        }
        ],
        "comments_count": 4854,
        "countries": [
          "美国"
        ],
        "directors": [{
          "avatars": {
            "large": null
          },
          "name": "斯蒂芬·卓博斯基"
        }],
        "genres": [
          "剧情",
          "家庭",
          "儿童"
        ],
        "id": 269,
        "images": {
          "large": "https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2507709428.jpg"
        },
        "original_title": "奇迹男孩 Wonder",
        "rating": {
          "average": 8.6,
          "max": 10,
          "min": 0,
          "stars": "45"
        },
        "reviews_count": 162,
        "summary": "电影《奇迹男孩》改编自全球畅销小说《奇迹》,讲述了一个温暖千万家庭的成长故事。10 岁的奥吉(雅各布·特伦布莱 Jacob Tremblay 饰)天生脸部畸形,此前一直在家中和妈妈(朱莉娅·罗伯茨 Julia Roberts 饰)自学。当他小学五年级时,奥吉进入父母为他精心挑选的学校上学。在这里,奥吉将与校长、老师以及性格迥异的同学相处,他不寻常的外表让他成为同学们讨论的焦点,并终日受到嘲笑和排斥,就连好不容易交到的新朋友也似乎不太值得信任。幸运的是,在成长过程中,奥吉的父母、姐姐一直是他最坚强的后盾,在他们的支持与关爱下,奥吉凭借自身的勇气、善良、聪敏影响激励了许多身边的人,并收获了友谊、尊重与爱,最终成长为大家心目中的不可思议的“奇迹”。",
        "title": "奇迹男孩 Wonder",
        "warning": "数据来源于网络整理,仅供学习,禁止他用。如有侵权请联系公众号:小楼昨夜又秋风。我将及时删除。",
        "wish_count": 29417,
        "year": 2017
      },
      {
        "casts": [{
          "avatars": {
            "large": "https://img3.doubanio.com/view/celebrity/s_ratio_celebrity/public/p20143.jpg"
          },
          "name": "丁晟"
        },
        {
          "avatars": {
            "large": "https://img1.doubanio.com/view/celebrity/s_ratio_celebrity/public/p1509429399.29.jpg"
          },
          "name": "王凯"
        },
        {
          "avatars": {
            "large": "https://img3.doubanio.com/view/celebrity/s_ratio_celebrity/public/p1422629943.25.jpg"
          },
          "name": "马天宇"
        }
        ],
        "comments_count": 6927,
        "countries": [
          "中国大陆",
          "香港"
        ],
        "directors": [{
          "avatars": {
            "large": null
          },
          "name": "丁晟"
        }],
        "genres": [
          "剧情",
          "动作"
        ],
        "id": 260,
        "images": {
          "large": "https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2508615612.jpg"
        },
        "original_title": "英雄本色2018",
        "rating": {
          "average": 5.2,
          "max": 10,
          "min": 0,
          "stars": "25"
        },
        "reviews_count": 711,
        "summary": "周凯(王凯 饰)参与走私,被身为缉毒警察的弟弟周超(马天宇 饰)逮捕入狱。三年后,周凯出狱,改过自新。曾经的手下阿仓(余皑磊 饰)已为毒贩头目,为获取周凯的海外客户资料,设计加害周凯。江湖中的好兄弟马柯(王大陆 饰)为了替周凯报仇,失去一条腿。自己的亲弟弟周超不相信哥哥周凯已金盆洗手,不断搜集证据,欲亲手逮捕周凯。最终,周凯与警方合作,逮捕了阿仓,两兄弟重归于好。",
        "title": "英雄本色2018",
        "warning": "数据来源于网络整理,仅供学习,禁止他用。如有侵权请联系公众号:小楼昨夜又秋风。我将及时删除。",
        "wish_count": 4552,
        "year": 2018
      }
      ]
    }
  }
})

在block.wxss文件中写入以下数据:

/* pages/movies/block/block.wxss */

/* 导入电影海报模板的WXSS */
@import "../poster/poster.wxss";

.block-title-bar {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  padding: 30rpx 20rpx 0rpx 20rpx;
}

.block-title {
  color: #333333;
  font-size: 30rpx;
}

.block-more {
  display: flex;
  flex-direction: row;
  align-items: center;
}

.block-more-text {
  color: #ffae00;
  font-size: 24rpx;
}

.block-more-icon {
  width: 12rpx;
  height: 20rpx;
  padding-left: 6rpx; 
}

.block-scroll-view {
  display: flex;
  flex-direction: row;
}

.block-movie-row{
  display: flex;
  flex-direction: row;
  padding: 20rpx 0 20rpx 20rpx;
}

.block-movie-card {
  display: flex;
  flex-direction: row;
  margin-right: 10rpx;
}

在block.wxml中写入:

<!--pages/movies/block/block.wxml-->

<!--导入电影海报模板-->
<import src="../poster/poster.wxml" />

<view>
  <!--区块顶栏-->
  <view class='block-title-bar'>
    <!--类别标题,如正在热映-->
    <text class='block-title'>{{blockTitle}}</text>
    <view class='block-more' catchtap='catchMore' data-title='{{blockTitle}}'>
      <!--更多-->
      <text class='block-more-text'>更多</text>
      <!--右箭头-->
      <image class='block-more-icon' src='/images/block/arrow-right.png'></image>
    </view>
  </view>
  <!--电影海报展示条-->
  <scroll-view scroll-x="{{true}}" class='block-scroll-view'>
    <view class='block-movie-row'>
      <block wx:for="{{blockMovies.subjects}}" wx:for-item="poster">
        <!--单个电影海报-->
        <view class='block-movie-card'>
          <template is="template-poster" data="{{...poster}}" />
        </view>
      </block>
    </view>
  </scroll-view>
</view>

按Ctrl+S保存后,模拟器效果如下。第一个出现了电影名称过长的情形,我们后续会写一个stringUtil.js的工具类来截断。

img

我们在block页面导入了poster模板的wxml和wxss文件,并通过下文代码使用了。

<!--导入电影海报模板-->
<import src="../poster/poster.wxml" />
……
<!--电影海报展示条-->
<scroll-view scroll-x="{{true}}" class='block-scroll-view'>
    <view class='block-movie-row'>
      <block wx:for="{{blockMovies.subjects}}" wx:for-item="poster">
        <!--单个电影海报-->
        <view class='block-movie-card'>
          <template is="template-poster" data="{{...poster}}" />
        </view>
      </block>
    </view>
  </scroll-view>
 ……

这里使用了一个新的控件滑动条,有scroll-x和scroll-y属性,用以设定滑动的方向是水平还是竖直。

在滑动条内部,通过wx:for循环取出blockMovies.subjects的值,用poster表示当前项。

电影海报通过海报模板导入,通过{{...poster}}的形式散开poster的值传入海报模板中,为海报模板中的同名变量提供对应的值。

<!--类别标题,如正在热映-->
<text class='block-title'>{{blockTitle}}</text>
<view class='block-more' catchtap='catchMore' data-title='{{blockTitle}}'>

在上文中又出现了catchtap和data-形式的属性名,这是界面上“更多”按钮的点击事件和自定义属性,后文详细讲解。

最后将block.js中data里的相关数据注释或删除,并在block.wxml里封装模板。

<!--导入电影海报模板-->
<import src="../poster/poster.wxml" />

<!--封装成区块模板-->
<template name="template-block">
    <view>
      ……
    </view>
</template>

别忘了在app.json文件的data属性中将主页的路径“pages/movies/index”提到首位,我们即将要编写主页了。

到目前为止,我们所需要的三个模板已经全部封装完了。

block模板里导入了poster模板,后者又嵌入了ratingbar模板,极大的精简了相关的代码,加强了代码的易维护性。不信?看主页,我们只需寥寥数行代码即可。

下篇:https://mp.weixin.qq.com/s/Isfk9s2cgtyXdh5Olap9QA

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 转载请注明出处, 谢谢! (~ o ~)Y 1月9日,也就是今天,微信推出的“小程序”正式上线。“小程序”是一种无...
    Jimmy_P阅读 14,373评论 53 273
  • 配置文件 app.json的配置(全局) {// 用来配置页面的路径"pages":["pages/index/i...
    Q轩哥阅读 27,087评论 2 31
  • 微信小程序在无论在功能、文档及相关支持方面,都是优于前面几种微信账号类型,它提供了很多原生程序才有的接口,使得我们...
    未央大佬阅读 2,290评论 0 12
  • 昨天看了一下微信小程序官方文档,总结一下自己学习的个人心得. 首先从官方文档给的框架说起,微信小程序官方文档给出了...
    Mr大大大阅读 47,139评论 9 68
  • 今天大组会,我们有几位同学没有到现场,这是我们真正意义上的最后一次大组会,也是三阶留给我的一个遗憾,虽然没有到现场...
    伏伏王阅读 162评论 0 6