# 如何开发电商类小程序 Vol\.3：数据加载和图文排版

- 作者：许诺
- 发布时间：2016-12-06 14:18
- 原文：https://www.ifanr.com/minapp/752901

![online-retail](https://s3.ifanr.com/wp-content/uploads/2016/12/online-retail.jpg)

这里是「电商类小程序实战教程」的第 3 篇文章。

上一期，知晓程序（微信号 zxcx0101）以爱范儿旗下的玩物志小程序为 Demo，介绍了商品列表的实现方法。

今天，我们将先完成上一期「加载更多列表」的功能，再来谈一谈**如何在商品详情页中，将商品描述从富文本数据转换成 JSON 数据后，并进行图文排版**。

### **「查看更多」功能**

我们还是先来看一下效果图：

![dianshang-1](https://s3.ifanr.com/wp-content/uploads/2016/12/dianshang-1.jpg)

**1. 调用 Toast 提示**

通过调用 `wx.showToast()` ，可以实现上图中部的 Toast 提示（加载中）。

微信提供的 Toast 组件有 `success` 和 `loading` 两种状态（旧版本的 `loading` 组件已经被废弃）。

```
/***list.js***/
wx.showToast({
 title: '成功', //提示的文字信息
 icon: 'success', //图标，只支持"success"、"loading"
 duration: 2000, //提示的延迟时间(毫秒)，最大为10000
 success: function() {}, //调用成功的回调函数
 faile: function() {}, //调用失败的回调函数

 //接口调用结束的回调函数（调用成功、失败都会执行）
 complete: function() {}
})
```

需要注意的是，**当 `showToast` 执行时，整个页面是不可点击的。**所以，不用考虑用户意外触发多次「查看更多」的问题。

调用 `wx.hideToast()` 即可隐藏 Toast 提示框。

现在，我们来思考一个问题。上面说到 `wx.showToast` 的 `duration` 最大值为 `10000` 毫秒，也就意味着**当请求时间大于 `10` 秒，若没有返回数据时，`loading` 提示框会自动消失**。

这会带来一些问题：

- 「查看更多」按钮重新变回可点击状态，可能造成请求的重复提交。
- 加载提示消失与数据成功加载之间出现时间差。

如何解决这个问题呢？其实很简单。

我们先给 `loading` 设置 `10000` 毫秒延迟时间，超过 `10000` 毫秒后，即使请求成功，也不保存任何数据。

同时，将按钮文字改回「查看更多」即可。

我们初始化一个 `timeoutFlag` 的对象，使用 `timer = setTimeout()` 设置 `9800` 毫秒延时。`9800` 毫秒后，以 `timer` 为 `key` 设置一个对象 `{ isTimeout: true }`，每次发起请求时都会有一个 `timer` 作为标识。

若请求成功时 `timeoutFlag.timer` 的值为 `{ isTimeout: true }`，则不处理请求成功后的数据。

**为什么要设置 `9800` 毫秒而不是 `10000` 毫秒呢？**

那是因为 `setTimeout` 的计时并不准确，我们给出 `200` 毫秒的提前量，以防止代码出错。

我们来封装一个 `_setTimeoutFlag` 方法：

```
/***list.js***/
_setTimeoutFlag: function () {
 var that = this;
 var timer = null;

 timer = setTimeout(function () {
  that.setData({
   tipText: '查看更多'
  });

  timeoutFlag.timer = { isTimeout: true };
 }, 9800);

 timeoutFlag.timer = { isTimeout: false };

 return timer;
}
```

**2. 构建与实现 `loadMore` 函数**

我们需要一个 `loadMore` 函数，来做到数据的分页查询。

一般，都是通过设置两个查询参数来控制，分别是每次请求条数 `limit` 和每次请求起始数 `offset`（即从第几条获取数据）。

以我们的需求为例：

- 第一次获取 20 条：`limit=20` &amp; `offset=0`
- 第二次再新增 10 条：`limit=10` 0&amp; `offset=20`
- 第三次再新增 10 条：`limit=10` &amp; `offset=30`

当然，根据各自不同的分页实现，`limit` 和 `offset` 的命名也不相同，我们只要了解原理即可。

根据以上逻辑，当用户点击「查看更多」按钮后，小程序在不同情况下应该给出相应的反应：

- 按钮文字变为「正在加载…」，页面弹出「加载中…」的 loading 提示框，设置 `offset + 10` 并发起请求
- 有新数据加载时，按钮文字变回「查看更多」，关闭 loading 提示框，保存当前 `offset`
- 无新数据加载时，按钮文字变为「没有更多啦」，关闭 loading 提示框
- 请求失败时，按钮文字变为「查看更多」，关闭 loading 提示框

先在 `list.wxml` 中设置按钮，并绑定点击事件 `loadMore;`。

```
/***list.wxml***/
<view class="btn-load-more" bindtap="loadMore">
 {{ tipText }}
</view>

```

根据上面的流程，我们来封装一下 `_getProductList` 和 `loadMore` 方法。

```
/***list.js***/
loadMore: function () {
 var that = this;
 
 this.setData({
  tipText: '正在加载...'
 });

 wx.showToast({
  title: '加载中...',
  icon: 'loading',
  duration: 10000
 });

 var timer = that._setTimeoutFlag();

 this._getMoreProductList(timer);
},

_getMoreProductList: function (timer) {
 var that = this;

 wx.request({
  url: CONFIG.API_URL.PRODUCT_LIST + this.data.orderBy,
  method: 'GET',
  data: {
   limit: 10,
   offset: this.data.offset + 10,
   shelf__id: this.data.shelfId,
   img_size: 'small'
  },

  success: function (data) {
   if (!timeoutFlag.timer.isTimeout) {
    if (data.data.objects.length > 1) {
     var objects = that.data.productList
                   .concat(data.data.objects);

     that.setData({
      productList: objects,
      offset: that.data.offset + 10,
      tipText: '查看更多'
     });
    } else {
     that.setData({
      tipText: '没有更多啦'
     });
    }
   }
   clearTimeout(timer);
   wx.hideToast();
  },

  fail: function () {
   that.setData({
    tipText: '查看更多'
   });
   wx.hideToast();
  }
 });
}

```

至此，我们已经完整实现了商品列表页的开发，接下来进入商品详情页。

### **商品详情图文排版**

在商品详情页中，我们主要来说一说图文排版的实现。

![artboard](https://s3.ifanr.com/wp-content/uploads/2016/12/Artboard.jpg)

目前商品详情的图文介绍，一般都是通过后台的富文本编辑器来排版创建的，数据都是以 HTML 代码的形式保存下来，然后在页面上直接渲染出来。

但是对于小程序来说，**这种 HTML 形式的数据不能直接拿来使用，因为小程序根本不能解析 HTML 代码**。

此时，我们需要处理由富文本编辑器所创建的数据，将其转换为 JSON 格式的数据，以便于在小程序里使用。

我们有两种思路去解决这个问题：

1. 由前端去解析 `html` 格式的数据，用正则匹配 HTML 标签，然后转换成 JSON 数据；
2. 交由后端去解析，同时新增一个专用的 API，返回解析过的 JSON 数据。

我们选用的是第二种方案，获取到的 JSON 数据格式如下:

```
{
 "description": [
  {
   "content": "xxx.jpg",
   "type": "image"
  },
  {
   "content": "商品描述文字",
   "type": "text"
  }
 ]
}
```

我们只要将获取的数据用 `wx:for` 渲染出来，渲染时判断 `type` 选择对应的 `text` 或 `image` 组件即可。

```
/***detail.wxml***/
<block wx:for="{{ description }}">
 <view
  class="description-img"
  wx:if="{{ item.type == 'image' }}">
  <image
   class="description-img-content"
   src="{{ item.content }}">
  </image>
 </view>
 <view
  class="description-text"
  wx:elif="{{ item.type == 'text' }}">
  <text class="description-text-content">
   {{ item.content }}
  </text>
 </view>
</block>

```

来看效果图：

![dianshang-2](https://s3.ifanr.com/wp-content/uploads/2016/12/dianshang-2.jpg)

仔细观察一下，我们可以发现，每一张图片的宽高都发生了不同程度的裁剪、拉伸或压缩。

这是由于 `image` 组件的默认宽高导致的，**我们在第一期说过，要特别留意小程序内置组件的默认样式，它们很可能会引起各种各样的问题。**

关注知晓程序（微信号 zxcx0101），回复「电商」，获取电商类小程序实战教程的往期文章。

我们的期望是，图片能够按照原有的宽高比例进行显示，并且每一张图片的宽度都能撑满整个内容区域。这应该如何处理呢？

这里就需要用到 `image` 组件的 `load` 事件，使用 `bindload` 属性将事件绑定在 `image` 组件中。

我们可以用 `event.detail = {height:'图片高度px', width:'图片宽度px'}` 的方式调整图片宽高尺寸。

我们定义一个 `imageLoad` 函数，作为 `image` 组件的 `load` 事件的处理函数，用 `data-index` 保存当前 `wx:for` 的 `index` 值作当前图片的索引值。

`detail.wxml` 改写 `image` 组件如下：

```
<image
 class="description-img-content"
 src="{{ item.content }}"
 bindload="imageLoad"
 data-index="{{ index }}">
</image>
```

`detail.js` 定义 `imageLoad` 函数如下：

```

data: {
  images: {}
},
imageLoad: function (e) {
  var realityWidth = e.detail.width;    //获取图片实际宽度
  var realityHeight = e.detail.height;  //获取图片实际高度

  //获取图片的实际宽高比例
  var proportion = realityWidth / realityHeight;  

  //设置图片的渲染宽度，750rpx - (2 * 40rpx) = 670rpx; 
  //父元素的左右 padding 都是 40rpx
  var viewWidth = 670;

  //按比例计算出图片的渲染高度
  var viewHeight = 670 / proportion;
  var images = this.data.images; 

  //将图片的 data-index 值作为 images 对象的 key
  //用来储存该图片的计算后的渲染宽高
  images[e.currentTarget.dataset.index] = {
    width: viewWidth,
    height: viewHeight
  };

  this.setData({
    images: images
  });
}

```

此时，我们就可以在 `image` 组件中获取到 `images[index].width` 和 `images[index].height`，并通过设置行内的 `style` 给每一个图片设置宽高了。

`detail.wxml` 改写 `image` 组件如下：

```
<image
 class="description-img-content"
 src="{{ item.content }}"
 bindload="imageLoad"
 data-index="{{ index }}"
 style="width:{{ images[index].width }}rpx;
        height:{{ images[index].height }}rpx">
</image>
```

完成的效果图如下：

![detail-image-good](https://s3.ifanr.com/wp-content/uploads/2016/11/detail-image-good.jpg)

### 本期内容总结

本期的「电商类小程序实战教程」就到这里了。

想跟本文作者在线交流小程序开发，关注微信号 zxcx0101，点击下方菜单栏「加群交流」即可。

最后，知晓程序（微信号 zxcx0101）来总结一下本期内容的重点：

**Toast 提示：**

- Toast 提示可以使用 wx.showToast() 来调用，使用 wx.hideToast() 隐藏。
- 开发者可以自定义 Toast 展示时间、展示文案等。
- Toast 最长展示时间不能超过 10000 毫秒，开发者需要做好超时处理工作。

 **图文排版：**

- 如果服务器中的数据是 HTML 数据，需要转换为 JSON，小程序才能解析。
- 直接将图片进行排版，图片宽高会出现问题。开发者需要处理好图片的宽高问题。

在下一期中，我们会结合 navTab 和 tabBar 组件，来谈一谈自定义组件的实现方式，请大家继续关注知晓程序。

**本文由知晓程序原创出品，关注微信号 zxcx0101，「电商」获取电商小程序实战指南的往期文章。**

快速关注知晓程序↓↓↓

![zxcx](https://s3.ifanr.com/wp-content/uploads/2016/11/zxcx-1.jpg)**知晓程序（微信号 zxcx0101）是爱范儿旗下专注于小程序生态的公众号。我们提供最全面、新鲜的小程序资讯（消息、观点、指南、活动）和服务，在这里你能了解到关于小程序的一切。**
