【小程序开发实战】信息流首页开发

984 阅读7分钟

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

前言

经过前面七篇的基础准备,我们已经具备了开发一个完整小程序的所有技能。从这篇开始,我们正式进入小程序真实功能的开发,让我们一同见证一个小程序是如何从开发到展现在用户眼前的。

首页开发

image.png

目前,我们的首页长这个样子,我们希望搜索框下方有一个可以不断加载内容的信息流列表,让我们着手来实现这个功能。

列表交互

通常一个移动端的信息流列表会有一个很好用的功能,叫下拉刷新,上拉加载。意思就是在内容区域顶部向下拉会使得整个内容列表进行刷新,在内容区域底部往上划,可以加载新的内容数据。

小程序页面默认有实现这一功能的相关属性,让我们看一下文档的具体介绍

image.png

让我们按照这个两个特性的具体文档,在我们的首页中进行实现

image.png

首先,让我们在需要开启下拉刷新的页面所在的json配置文件中声明开启这项功能

image.png

接着,我们在js文件中分别实现下拉刷新和上拉加载的逻辑

下拉刷新

async refresh() {
  try {
    const { result } = await this.getData()
    if (result) {
      const { data, totalSize } = result
      const { pageSize } = this.data
      const noMoreData = totalSize < pageSize
      const newData = {
        noMoreData
      }
      if (data?.length) {
        Object.assign(newData, {
          listData: data
        })
      }
      this.setData(newData)
    }
  } catch (error) {
    console.log('refresh fail', error)
  }
}

上拉加载

async loadMore() {
  try {
    const { result } = await this.getData(this.pageNo)
    if (result) {
      const { data, totalSize } = result
      const { pageNo, pageSize, listData } = this.data
      const noMoreData = totalSize < pageSize
      const newData = {
        noMoreData
      }
      if (data?.length) {
        Object.assign(newData, {
          listData: listData.concat(data),
          pageNo: pageNo + 1
        })
      }
      this.setData(newData)
    }
  } catch (error) {
    console.log('loadmore fail', error)
  }
}

代码解析

既然是一个可以一直向上滑动加载新内容的列表,那么数据量自然会非常大,所以我们不可能一次性请求所有的数据,而是每次请求20条新内容拼接到前面的内容后面。

云函数改造

因此我们也需要改造提供数据的云函数逻辑,使得其可以按照指定每次的查询位置和页大小。

首先,我们需要在云函数内部解析收到的关于分页的参数,我们启动云函数的本地调试模式,然后在函数第一步设置一个调试断点,观察函数接收到的参数是怎样的。

image.png

可以看到,这里已经接收到了前面传递的有关分页的参数,那么我们根据这个参数去做数据库的查询即可。改造后的云函数实现如下

exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()

  let data = []
  let totalSize = 0

  const { pageNo, pageSize } = event
  if (pageNo === undefined || !pageSize) {
    throw new Error('pageNo and pageSize is required')
  }

  const db = cloud.database()

  try {
    const countResult = await db.collection('homeContentList').count()
    const { total } = countResult
    totalSize = total
    const { data: listData } = await db.collection('homeContentList')
                                       .skip(pageNo)
                                       .limit(pageSize)
                                       .get()
    if (listData) {
      data = listData
    }
  } catch (error) {
    console.log('error', error)
  }

  return {
    event,
    openid: wxContext.OPENID,
    appid: wxContext.APPID,
    unionid: wxContext.UNIONID,
    data,
    totalSize
  }
}

这里使用了skiplimit方法的联合使用,做到了对于数据库中的数据按照分页进行查询,详细使用方法可见 官方文档

新增数据

image.png

虽然我们的列表目前支持下拉刷新和上拉加载,但我们数据库中的数据还不足以我们体验到这一交互的真实使用,所以我们需要增加一个新增数据的功能,即用户可以在小程序中向数据库中新增内容。

首页改造

我们对现在的页面稍微改造一下,为其增加一个新增内容的入口

<view>
  <view>
    <van-search
      value="{{ searchValue }}"
      input-align="center"
      shape="round"
      placeholder="请输入搜索关键词"
      use-action-slot
    >
      <view class="add-wrap" slot="action" bind:tap="addContent">
        <van-icon name="add" size="60rpx" />
      </view>
    </van-search>
    
  </view>
  <content-list items="{{listData}}"></content-list>
  <view wx:if="{{ noMoreData }}">没有更多数据了</view>
</view>

图标组件使用

我们在原有的搜索框位置末端增加了一个加号图标,这里使用了vant内置的图标组件,引入如下

{
  "usingComponents": {
    "van-search": "@vant/weapp/search/index",
    "van-icon": "@vant/weapp/icon/index",
    "content-list": "../../components/content-list"
  },
  "enablePullDownRefresh": true
}

同时,由于我们改变了图标的默认大小,还修改了布局,所以要为图标组件的容器增加样式调整,以使其垂直方向居中

.add-wrap {
  display: flex;
  align-items: center;
}

改造后的页面如下

image.png

接着,我们需要为点击添加图标后增加响应事件,即跳转到一个新的页面,用于填写内容。

新增内容发布页面

新增页面时,我们只需要在项目的app.jsonpages一项增加内容,开发者工具则会为我们自动创建好一个新页面的相关文件。

image.png

页面跳转逻辑

最后,我们为添加图标点击后的事件响应如下,在点击新增图标后则会跳转到我们新增的add页面

image.png

内容发布页开发

页面开发

image.png

我们使用到vant的图片上传组件uploader、按钮组件button以及小程序原生提供的文本输入组件textarea

{
  "usingComponents": {
    "van-uploader": "@vant/weapp/uploader/index",
    "van-button": "@vant/weapp/button/index"
  }
}

样式方面我们仅对页面增加一个四周的内边距,以及按钮距离上方的外边距设置

.wrap {
  padding: 40rpx;
}
.submit-btn {
  margin-top: 40rpx;
}

图片上传

接下来是重点也是难点,就是图片上传能力的实现。首先,既然我们使用vant组件来完成图片上传功能,就来看看它的文档关于这部分的介绍

image.png

这里我们需要为uploader组件定义图片上传后的事件响应,我们按照vant文档所提供是示例实现即可。

这里的逻辑是用户首先通过小程序自带的上传功能将本地文件进行上传,然后小程序会获得一个文件的临时地址。

接着,开发者拿着这个临时地址作为文件的标识,去将文件真正上传到云端,上传完成后会拿到文件所在云端的地址。

数据提交

实现了图片上传后,我们还需要处理一下文本输入框所输入内容的解析以及提交按钮点击后的数据提交逻辑

handleSubmit() {
  const { inputValue, fileList } = this.data
  if (inputValue || fileList.length) {
    const image = fileList.map(file => file.url)
    this.addContent(inputValue, image)
  }
},

async addContent(text, image) {
  try {
    const data = await wx.cloud.callFunction({
      name: 'addContent',
      data: {
        text,
        image
      }
    })
  } catch (error) {
    console.log('add fail', error)
  }
},

这里handleSumbit用于处理按钮点击的事件响应,对文字内容和图片内容进行了判断和解析,并调用新增内容的云函数完成内容的提交。

最终我们的模板部分如下

<view class="wrap">
  <textarea
    placeholder="分享你的内容..."
    auto-focus
    confirm-type="return"
    value="{{ inputValue }}"
    bindinput="handleInput"
  />
  <van-uploader
    file-list="{{ fileList }}"
    bind:after-read="uploadToCloud"
    max-count="9"
    image-fit="aspectFill"
    multiple
  />
  <view class="submit-btn">
    <van-button
      type="info"
      block
      round
      disabled="{{ btnDisable }}"
      bind:click="handleSubmit"
    >发布</van-button>
  </view>
</view>

新增内容云函数

我们在cloudfuncionts目录右键,选择新建 Node.js 云函数创建一个叫addContent的云函数,用于处理内容提交。

具体实现如下,我们对该函数进行了参数的获取,即提交的文字内容和图片内容。然后使用数据库的add方法进行记录的新增,并为本次提交增加一个createTime字段标识提交的时间。

image.png

数据库改造

由于我们之前在数据库中手动添加的记录都是一段文字和一张图片,而这里我们支持了图片的多张上传,所以我们改造一下之前的记录。

image字段由string改为array,我们可以点击之前的记录中的image字段后的修改图标,然后进入下图所示,将类型进行修改即可。

image.png

列表组件改造

既然数据内容的格式修改了,我们用于展示内容的列表组件也需要随之改造。

image.png

之前的内容组件只会根据image字段进行图片的展示,当我们的image字段改为数组类型后,我们可以改为通过wx:for循环遍历image的内容进行展示。

<view>
  <view class="item-wrap" wx:for="{{items}}" wx:key="index">
    <text class="item-text">{{item.text}}</text>
    <view
      class="image-wrap"
      wx:for="{{ item.image }}"
      wx:for-item="imageItem"
      wx:for-index="imageIndex"
      wx:key="imageIndex"
    >
      <image class="item-image" src="{{ imageItem }}" mode="aspectFill"></image>
    </view>
  </view>
</view>

同样,由于图片可能展示多张,所以我们的布局也要做一定的调整来适应多张图片的展示。

.item-wrap {
  margin: 20rpx;
  padding: 20rpx;
  display: flex;
  flex-direction: column;
}
.item-text {
  display: inline-block;
  font-size: 28rpx;
  font-weight: 600;
  color: #333;
  flex: 1;
}
.image-wrap {
  margin-top: 20rpx;
  width: 200rpx;
  height: 200rpx;
}
.item-image {
  border-radius: 10rpx;
  width: 100%;
  height: 100%;
}

最后效果如下

image.png

页面联动

经过上面对于首页的改造、内容列表组件的改造以及新增内容页面的开发,我们已经可以形成从新增内容到回到首页列表的联动交互。

返回刷新

这里只需要我们在新增内容的云函数执行成功后通过调用小程序页面返回的APIwx.navigateBack()回到首页即可,因为我们在首页的onShow方法内每次都会请求最新的内容列表。

image.png

在完成内容查询和内容新增两个云函数的本地调试后,我们可以分别右键云函数的目录将其上传和部署,实现云函数真正同步到云端服务。这样,我们在调用云函数时,不用通过本地调试的方法也能真正请求到云端的服务。

加载优化

最后,我们稍微调整一下上拉加载的逻辑,增加如下一行代码,控制在没有更多数据时不再继续查询。

image.png

总结

今天的内容比较多,但是经过这些内容的完成,我们实现一个具有联动交互效果的首页和内容发布页。截止目前,我们的小程序已具备如下功能,并且有两个云函数分别支持内容列表的分页返回以及内容的新增。

  • 首页支持内容列表展示、下拉刷新、上拉加载
  • 新增内容页支持输入多行文本及图片上传

后续我们将继续完善小程序功能,并结合实际开发讲解过程中所遇到的问题以及各种工具的使用。