The foreword 0.

This article will teach you how to write lines of code for the mini micro blog, which includes the following features:

  • Feed stream: Focus on the dynamic, all the dynamic
  • Send text dynamic
  • Search the user
  • Pay attention to the system
  • Thumb up dynamic
  • Personal home page

Cloud development capabilities used:

  • Cloud database
  • Cloud storage
  • Cloud function
  • Cloud call

Yes, almost all cloud development capabilities. That is to say, after reading this actual combat, you are equivalent to a complete introduction to cloud development!

Well, of course, this is actually just an introduction to the core logic and key snippets of code, the complete code is recommended to download.

1. Obtain authorization

As a social platform, the first thing to do is to obtain user information through user authorization. The small program provides a very convenient interface:

<button open-type="getUserInfo" bindgetuserinfo="getUserInfo">Enter the small circle</button>
Copy the code

This button has an open-type property, which is specifically designed to use the open capability of the applittle, and getUserInfo, which means to getuser information, which can be obtained from the bindgetUserInfo callback.

So we can put the button in WXML and write the following code in the corresponding JS:

Page({
  ...

  getUserInfo: function(e) {
    wx.navigateTo({
      url: "/pages/circle/circle"})},... })Copy the code

After successfully obtaining the user information, we can jump to the mini micro blog page.

Note that you cannot use wx.authorize({scope: “scope.userinfo “}) to obtain permissions for reading user information because it does not pop out of the authorization popover. For now, it can only be implemented in the way described above.

2. Home page design

The home page of the social platform is much the same, mainly composed of three parts:

  • The Feed flow
  • The message
  • Personal information

It’s easy to think of a layout like this (make sure you create a new Page: pages/circle/circle.wxml) :

<view class="circle-container">
  <view
    style="display:{{currentPage === 'main' ? 'block' : 'none'}}"
    class="main-area"
  >
  </view>

  <view
    style="display:{{currentPage === 'msg' ? 'flex' : 'none'}}"
    class="msg-area"
  >
  </view>

  <view
    style="display:{{currentPage === 'me' ? 'flex' : 'none'}}"
    class="me-area"
  >
  </view>

  <view class="footer">
    <view class="footer-item">
      <button
        class="footer-btn"
        bindtap="onPageMainTap"
        style="background: {{currentPage === 'main' ? '# 111' : 'rgba (0,0,0,0)'}}; color: {{currentPage === 'main' ? '#fff' : '#000'}}"
      >Home page</button>
    </view>
    <view class="footer-item">
      <button
        class="footer-btn"
        bindtap="onPageMsgTap"
        style="background: {{currentPage === 'msg' ? '# 111' : 'rgba (0,0,0,0)'}}; color: {{currentPage === 'msg' ? '#fff' : '#000'}}"
      >The message</button>
    </view>
    <view class="footer-item">
      <button
        class="footer-btn"
        bindtap="onPageMeTap"
        style="background: {{currentPage === 'me' ? '# 111' : 'rgba (0,0,0,0)'}}; color: {{currentPage === 'me' ? '#fff' : '#000'}}"
      >personal</button>
    </view>
  </view>
</view>
Copy the code

It makes sense that the screen is divided into two parts: the top part is the main content, and the bottom part is the three-tab Footer. Key WXSS implementation (complete WXSS can be downloaded to view the source code) :

.footer {
  box-shadow: 0 0 15rpx #ccc;
  display: flex;
  position: fixed;
  height: 120rpx;
  bottom: 0;
  width: 100%;
  flex-direction: row;
  justify-content: center;
  z-index: 100;
  background: #fff;
}

.footer-item {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 33.33%;
  color: # 333;
}

.footer-item:nth-child(2) {
  border-left: 3rpx solid #aaa;
  border-right: 3rpx solid #aaa;
  flex-grow: 1;
}

.footer-btn {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 0;
  font-size: 30rpx;
}
Copy the code

The core logic is to keep the Footer underneath with position: fixed.

The reader will notice that there is a currentPage data, and the function of this data is actually straightforward: determine the main content by determining which of the main/ MSG /me values it has. Also, to let first-time users know which Tab they are on, the corresponding button in the Footer will have black text on a white background in contrast to the other two tabs.

Now let’s take a look at the code in the main section (expanding on the code above) :

.<view
  class="main-header"
  style="display:{{currentPage === 'main' ? 'flex' : 'none'}}; max-height:{{mainHeaderMaxHeight}}"
>
  <view class="group-picker-wrapper">
    <picker
      bindchange="bindGroupPickerChange"
      value="{{groupArrayIndex}}"
      range="{{groupArray}}"
      class="group-picker"
    >
      <button class="group-picker-inner">
        {{groupArray[groupArrayIndex]}}
      </button>
    </picker>
  </view>
  <view class="search-btn-wrapper">
    <button class="search-btn" bindtap="onSearchTap">Search the user</button>
  </view>
</view>
<view
  class="main-area"
  style="display:{{currentPage === 'main' ? 'block' : 'none'}}; height: {{mainAreaHeight}}; margin-top:{{mainAreaMarginTop}}"
>
  <scroll-view scroll-y class="main-area-scroll" bindscroll="onMainPageScroll">
    <block
      wx:for="{{pageMainData}}"
      wx:for-index="idx"
      wx:for-item="itemName"
      wx:key="_id"
    >
      <post-item is="post-item" data="{{itemName}}" class="post-item-wrapper" />
    </block>
    <view wx:if="{{pageMainData.length === 0}}" class="item-placeholder"
      >No data</view
    >
  </scroll-view>
  <button
    class="add-poster-btn"
    bindtap="onAddPosterTap"
    hover-class="add-poster-btn-hover"
    style="bottom:{{addPosterBtnBottom}}"
  >
    +
  </button>
</view>.Copy the code

List rendering and conditional rendering are used here, so if you don’t know, you can click there to learn about them.

As you can see, I’ve added a header compared to the previous code, and I’ve also added a new ScrollView inside the main-area (to show the Feed flow) and a button (to edit the new mini tweet). The header’s function is simple: The left area is a picker that selects the dynamic type to view (currently there are focus dynamic and all dynamic); The area on the right is a button, which can be clicked to jump to the search page. Let’s play these two functions first and continue to see the new content in the main-area first.

The scrollView in the main-area is a list of scrollevents that can be listened for.

data: {
  ...
  addPosterBtnBottom: "190rpx".mainHeaderMaxHeight: "80rpx".mainAreaHeight: "calc(100vh - 200rpx)".mainAreaMarginTop: "80rpx",},onMainPageScroll: function(e) {
  if (e.detail.deltaY < 0) {
    this.setData({
      addPosterBtnBottom: "-190rpx".mainHeaderMaxHeight: "0".mainAreaHeight: "calc(100vh - 120rpx)".mainAreaMarginTop: "0rpx"})}else {
    this.setData({
      addPosterBtnBottom: "190rpx".mainHeaderMaxHeight: "80rpx".mainAreaHeight: "calc(100vh - 200rpx)".mainAreaMarginTop: "80rpx"})}},...Copy the code

Combined with WXML, headers and buttons “pop out” when the page is swiped down (deltaY < 0), and “pop in” when they are not. For a better visual transition, we can use transition in WXSS:

..main-area {
  position: relative;
  flex-grow: 1;
  overflow: auto;
  z-index: 1;
  transition: height 0.3 s, margin-top 0.3 s;
}
.main-header {
  position: fixed;
  width: 100%;
  height: 80rpx;
  background: #fff;
  top: 0;
  left: 0;
  display: flex;
  justify-content: space-around;
  align-items: center;
  z-index: 100;
  border-bottom: 3rpx solid #aaa;
  transition: max-height 0.3 s;
  overflow: hidden;
}
.add-poster-btn {
  position: fixed;
  right: 60rpx;
  box-shadow: 5rpx 5rpx 10rpx #aaa;
  display: flex;
  justify-content: center;
  align-items: center;
  color: # 333;
  padding-bottom: 10rpx;
  text-align: center;
  border-radius: 50%;
  font-size: 60rpx;
  width: 100rpx;
  height: 100rpx;
  transition: bottom 0.3 s;
  background: #fff;
  z-index: 1; }...Copy the code

3. The Feed stream

Post – item 3.1

As mentioned earlier, the content of a Scrollview is a Feed stream, so the first thing to think about is using list rendering. Furthermore, each item in the list rendering is abstracted for ease of reuse on the personal home page. This is where the Custom Component function in the applittle comes in.

Setting-up a post – Component of the item, including the realization of the WXML (path: pages/circle/Component/post – item/post – item. Js) :

<view
  class="post-item"
  hover-class="post-item-hover"
  bindlongpress="onItemLongTap"
  bindtap="onItemTap"
>
  <view class="post-title">
    <view class="author" hover-class="author-hover" catchtap="onAuthorTap"
      >{{data.author}}</view
    >
    <view class="date">{{data.formatDate}}</view>
  </view>
  <view class="msg-wrapper">
    <text class="msg">{{data.msg}}</text>
  </view>
  <view class="image-outer" wx:if="{{data.photoId ! = = '}}" catchtap="onImgTap">
    <image-wrapper is="image-wrapper" src="{{data.photoId}}" />
  </view>
</view>
Copy the code

A poster-item is a poster-item that contains the following information:

  • The author’s name
  • Send time
  • The text content
  • Image content

The image content, because it is optional, uses conditional rendering, which prevents the image display area from taking up screen space when there is no image information. In addition, image content is mainly composed of image-wrapper, which is also a Custom Component. The main functions are:

  • Force the image to be cropped at 1:1
  • Click to see a larger image
  • Loading is displayed when loading is not complete

The code is not shown here. It is simple and can be found in component/ image-Wrapper.

Looking back at the other additions to the main-area, the attentive reader will notice this:

<view wx:if="{{pageMainData.length === 0}}" class="item-placeholder"
  >No data</view
>
Copy the code

This will prompt the user if the Feed stream does not retrieve data for a while.

3.2 Collections: Poster, poster_users

The section showing the Feed stream is written; all that’s left is the actual data. According to the main information of Poster -item in the previous section, we can preliminarily infer that a mini-microblog is stored in the Collection Poster in the cloud database as follows:

{
  "username": "Tester"."date": "The 2019-07-22 12:00:00"."text": "Ceshiwenben"."photo": "xxx"
}
Copy the code

Let’s start with username. Since social platforms generally do not limit users’ nicknames, if each mini weibo is stored with a nickname, then every time a user changes his or her nickname in the future, he or she will have to go through the database to change all the mini Weibo items, which is quite time-consuming. Therefore, we should store a userId instead. The ids and nicknames are stored separately in a separate collection called POSTER_users.

{ "userId": "xxx", "name": "Tester", ... (Other user information)}Copy the code

Where do I get the userId? Of course, through the previously authorized access to user information interface, the details of the operation will be discussed later.

The next is date, which is best used as the server time (because there may be errors in the time sent from the client), and the corresponding interface is provided in the cloud development documentation: serverDate. This data can be used directly by new Date() and can be interpreted as a UTC time.

Text indicates the text information, which can be stored directly.

Photo represents attached image data, but limited to the implementation of the image element in the small program, if you want to display an image, either provide the URL of the image or provide the id of the image in the cloud storage, so the best practice here is to upload the image to the cloud storage first and then store the file ID in the callback as the data.

To sum up, the data structure of each poster item is as follows:

{
  "authorId": "xxx"."date": "utc-format-date"."text": "Ceshiwenben"."photoId": "yyy"
}
Copy the code

With the data structure determined, we can start adding data to the collection. But before we can do that, we are missing an important step.

3.3 User information input and cloud database

True, we haven’t added a single new user to Poster_Users yet. This step is usually determined when the pages/ Circle/Circle page first loads:

getUserId: function(cb) {
  let that = this
  var value = this.data.userId || wx.getStorageSync("userId")
  if (value) {
    if (cb) {
      cb(value)
    }
    return value
  }
  wx.getSetting({
    success(res) {
      if (res.authSetting["scope.userInfo"]) {
        wx.getUserInfo({
          withCredentials: true.success: function(userData) {
            wx.setStorageSync("userId", userData.signature)
            that.setData({
              userId: userData.signature
            })
            db.collection("poster_users")
              .where({
                userId: userData.signature
              })
              .get()
              .then(searchResult= > {
                if (searchResult.data.length === 0) {
                  wx.showToast({
                    title: "New user entry"
                  })
                  db.collection("poster_users")
                    .add({
                      data: {
                        userId: userData.signature,
                        date: db.serverDate(),
                        name: userData.userInfo.nickName,
                        gender: userData.userInfo.gender
                      }
                    })
                    .then(res= > {
                      console.log(res)
                      if (res.errMsg === "collection.add:ok") {
                        wx.showToast({
                          title: "Entry completed"
                        })
                        if (cb) cb()
                      }
                    })
                    .catch(err= > {
                      wx.showToast({
                        title: "Entry failed, please try again later".image: "/images/error.png"
                      })
                      wx.navigateTo({
                        url: "/pages/index/index"})})}else {
                  if (cb) cb()
                }
              })
          }
        })
      } else {
        wx.showToast({
          title: "Login has expired. Please reauthorize login.".image: "/images/error.png"
        })
        wx.navigateTo({
          url: "/pages/index/index"})}}})}Copy the code

Code implementation is more complex, the overall idea is this:

  1. Determine if it has been storeduserIdIf there is a direct return and call callback function, if there is no continue 2
  2. throughwx.getSettingGets the current Settings
  3. If it’s in the returnres.authSetting["scope.userInfo"]If the user is authorized to read the user information, go to 3. If the user is not authorized, go back to the home page to re-authorize the user information
  4. callwx.getUserInfoObtain user information and extract it successfullysignature(this is the unique signature of each wechat user), and callwx.setStorageSyncThe cache
  5. calldb.collection().where().get()If not, it indicates that the user has entered the datawhere()), if yes indicates that the user is a new user, proceed to 5
  6. Prompt new user entry, while callingdb.collection().add()To add the user information, and finally determine whether the input is successful through the callback, and prompt the user

Before you know it, we’re using the cloud database feature in cloud development, and then we’re starting to use cloud storage and cloud functions!

3.4 addPoster and Cloud Storage

To send a new mini tweet, you need an interface to edit the new mini tweet. I’ll use the path pages/ Circle/add-Poster/Add-poster:

<view class="app-poster-container">
  <view class="body">
    <view class="text-area-wrapper">
      <textarea bindinput="bindTextInput" placeholder="Fill in here" value="{{text}}" auto-focus="true" />
      <view class="text-area-footer">
        <text>{{remainLen}}/140</text>
      </view>
    </view>
    <view bindtap="onImageTap" class="image-area">
      <view class="image-outer">
        <image-wrapper is="image-wrapper" src="{{imageSrc}}" placeholder="Select pictures to upload" />
      </view>
    </view>
  </view>
  <view class="footer">
    <button class="footer-btn" bindtap="onSendTap">send</button>
  </view>
</view>
Copy the code

The WXML code is easy to understand: the textarea displays the edited text, the image-wrapper displays the image to be uploaded, and at the bottom is a send button. Where, the bindTap event in the image editing area is implemented:

onImageTap: function() {
  let that = this
  wx.chooseImage({
    count: 1.success: function(res) {
      const tempFilePaths = res.tempFilePaths
      that.setData({
        imageSrc: tempFilePaths[0]})}})}Copy the code

Get the temporary path to the local image directly from the official WX. chooseImage API. When the send button is clicked, the following code is executed:

onSendTap: function() {
  if (this.data.text === "" && this.data.imageSrc === "") {
    wx.showModal({
      title: "Error".content: "Cannot send empty content.".showCancel: false.confirmText: "Good"
    })
    return
  }
  const that = this
  wx.showLoading({
    title: "In transit".mask: true
  })
  const imageSrc = this.data.imageSrc
  if(imageSrc ! = ="") {
    const finalPath = imageSrc.replace("/ /"."/").replace(":"."")
    wx.cloud
      .uploadFile({
        cloudPath: finalPath,
        filePath: imageSrc // File path
      })
      .then(res= > {
        that.sendToDb(res.fileID)
      })
      .catch(error= > {
        that.onSendFail()
      })
  } else {
    that.sendToDb()
  }
},
sendToDb: function(fileId = "") {
  const that = this
  const posterData = {
    authorId: that.data.userId,
    msg: that.data.text,
    photoId: fileId,
    date: db.serverDate()
  }
  db.collection("poster")
    .add({
      data: {
        ...posterData
      }
    })
    .then(res= > {
      wx.showToast({
        title: "Sent successfully"
      })
      wx.navigateBack({
        delta: 1
      })
    })
    .catch(error= > {
      that.onSendFail()
    })
    .finally(wx.hideLoading())
}
Copy the code
  1. First determine whether the text and image content are empty, if yes, do not send, if not proceed to 2
  2. When uploading an image to the cloud storage system, you need to replace some special characters in the temporary URL in the image. For reasons, see The file name Naming restrictions
  3. When the upload is successful, calldb.collection().add()After the message is successfully sent, return to the previous page (the home page). If the message fails to be sent, perform the operationonSendFailFunction, the latter see the source code, logic is simple here do not repeat

And that’s how we created our first mini-blog. Let it show up in the Feed stream!

3.5 Cloud function getMainPageData

The main purpose of this function, as mentioned earlier, is to process the data in the cloud database and return the final data to the client, which visualizes the data to the user. Let’s start with a preliminary version, and since poster_Users only has one piece of data right now, we’ll just show you your own mini tweet. The code of the getMainPageData cloud function is as follows:

// Cloud function entry file
const cloud = require("wx-server-sdk")
cloud.init()
const db = cloud.database()

// Cloud function entry function
exports.main = async (event, context, cb) => {
  // Get the input parameter from event
  const userId = event.userId
  let followingResult
  let users
  // idNameMap stores the mapping between userId and name
  let idNameMap = {}
  let followingIds = []
  // Obtain user information
  followingResult = await db
      .collection("poster_users")
      .where({
        userId: userId
      })
      .get()
    users = followingResult.data
    followingIds = users.map(u= > {
      return u.userId
    })
  users.map(u= > {
    idNameMap[u.userId] = u.name
  })
  // Get dynamic
  const postResult = await db
    .collection("poster")
    .orderBy("date"."desc")
    .where({
      // Use the advanced filtering function to filter out the userids that meet the conditions
      authorId: db.command.in(followingIds)
    })
    .get()
  const postData = postResult.data
  // Add a formatDate attribute to the returned data, which stores the author attribute of the user's nickname, and a formatDate attribute that stores the formatted time
  postData.map(p= > {
    p.author = idNameMap[p.authorId]
    p.formatDate = new Date(p.date).toLocaleDateString("zh-Hans", options)
  })
  return postData
}
Copy the code

Pages /circle/circle.js

getMainPageData: function(userId) {
  const that = this
  wx.cloud
    .callFunction({
      name: "getMainPageData".data: {
        userId: userId,
        isEveryOne: that.data.groupArrayIndex === 0 ? false : true
      }
    })
    .then(res= > {
      that.setData({
        pageMainData: res.result,
        pageMainLoaded: true
      })
    })
    .catch(err= > {
      wx.showToast({
        title: "Obtaining dynamic failure".image: "/images/error.png"
      })
      wx.hideLoading()
    })
}
Copy the code

You can display the Feed stream data to the user.

Later, getMainPageData will also be used according to the different scenarios, added the query of all users dynamic, query concerned about the user dynamic function, but the principle is the same, look at the source code can be easily understood, the follow-up is no longer explained.

4. Focus on the system

In the last section, we ran through most of the major features of cloud development: cloud database, cloud storage, cloud functions, and cloud calls, and then the rest of the implementation depends on them.

4.1 poster_user_follows

First we need to create a new collection poster_user_follows, which has the following data structure for each item:

{
  "followerId": "xxx"."followingId": "xxx"
}
Copy the code

Simply, followerId means follower, followingId means follower.

4.2 the user data page

If you want to follow or unfollow someone, you need to go to their home page. So let’s put a user-info component in pages/circle/user-data/user-data.wxml and create a new component to edit it:

<view class="user-info">
  <view class="info-item" hover-class="info-item-hover">UserName: {{userName}}</view>
  <view class="info-item" hover-class="info-item-hover" bindtap="onPosterCountTap">Dynamic number: {{posterCount}}</view>
  <view class="info-item" hover-class="info-item-hover" bindtap="onFollowingCountTap">{{followingCount}}</view>
  <view class="info-item" hover-class="info-item-hover" bindtap="onFollowerCountTap">{{followerCount}}</view>
  <view class="info-item" hover-class="info-item-hover" wx:if="{{originId && originId ! == '' && originId ! == userId}}"><button bindtap="onFollowTap">{{followText}}</button></view>
</view>
Copy the code

Note here the conditionally rendered button: the button will not be rendered if the current userId (originId) and the accessed userId (userId) are equal (you cannot follow/unfollow yourself).

Let’s focus on the implementation of onFollowTap:

onFollowTap: function() {
  const that = this
  // Determine the current state of concern
  if (this.data.isFollow) {
    wx.showLoading({
      title: "In operation".mask: true
    })
    wx.cloud
      .callFunction({
        name: "cancelFollowing".data: {
          followerId: this.properties.originId,
          followingId: this.properties.userId
        }
      })
      .then(res= > {
        wx.showToast({
          title: "Unfollow success"
        })
        that.setData({
          isFollow: false.followText: "Attention"
        })
      })
      .catch(error= > {
        wx.showToast({
          title: "Unfollow failure".image: "/images/error.png"
        })
      })
      .finally(wx.hideLoading())
  } else if (this.data.isFollow ! = =undefined) {
    wx.showLoading({
      title: "In operation".mask: true
    })
    const data = {
      followerId: this.properties.originId,
      followingId: this.properties.userId
    }
    db.collection("poster_user_follows")
      .add({
        data: {
          ...data
        }
      })
      .then(res= > {
        wx.showToast({
          title: "Focus on success"
        })
        that.setData({
          isFollow: true.followText: "Unfollow"
        })
      })
      .catch(error= > {
        wx.showToast({
          title: "Focus on failure".image: "/images/error.png"
        })
      })
      .finally(wx.hideLoading())
    }
  }
}
Copy the code

Why do you call db.collection().add() when you are concerned, but call the cloud function when you are unconcerned? This is where the design of a cloud database comes in: the operation of deleting multiple pieces of data, or deleting data filtered using WHERE, can only be performed on the server side. If you do want to delete the data on the client, you can use setData to save the _id of the unique data when querying the user relationship, and then use db.collection().doc(_id).delete() to delete the data. The reader can choose between these two implementations. Of course, there’s another implementation that doesn’t actually delete the data, but just tags it with an isDelete field.

The implementation of querying user relationship is very simple. The implementation of cloud function is as follows:

// Cloud function entry file
const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database()

// Cloud function entry function
exports.main = async(event, context) => {
  const followingResult = await db.collection("poster_user_follows")
    .where({
      followingId: event.followingId,
      followerId: event.followerId
    }).get()
  return followingResult
}
Copy the code

The client simply checks to see if the length of the returned data is greater than 0.

In addition, the cloud function implementation of obtaining other data in the user-data page is attached:

// Cloud function entry file
const cloud = require("wx-server-sdk")
cloud.init()
const db = cloud.database()

async function getPosterCount(userId) {
  return {
    value: (await db.collection("poster").where({
      authorId: userId
    }).count()).total,
    key: "posterCount"}}async function getFollowingCount(userId) {
  return {
    value: (await db.collection("poster_user_follows").where({
      followerId: userId
    }).count()).total,
    key: "followingCount"}}async function getFollowerCount(userId) {
  return {
    value: (await db.collection("poster_user_follows").where({
      followingId: userId
    }).count()).total,
    key: "followerCount"}}async function getUserName(userId) {
  return {
    value: (await db.collection("poster_users").where({
      userId: userId
    }).get()).data[0].name,
    key: "userName"}}// Cloud function entry function
exports.main = async (event, context) => {
  const userId = event.userId
  consttasks = [] tasks.push(getPosterCount(userId)) tasks.push(getFollowerCount(userId)) tasks.push(getFollowingCount(userId))  tasks.push(getUserName(userId))const allData = await Promise.all(tasks)
  const finalData = {}
  allData.map(d= > {
    finalData[d.key] = d.value
  })
  return finalData
}
Copy the code

It is easy to understand that the client can get the return and use it directly.

Search the page

This part is actually pretty easy to implement. The key search function is implemented as follows:

// Cloud function entry file
const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database()

const MAX_LIMIT = 100
async function getDbData(dbName, whereObj) {
  const totalCountsData = await db.collection(dbName).where(whereObj).count()
  const total = totalCountsData.total
  const batch = Math.ceil(total / 100)
  const tasks = []
  for (let i = 0; i < batch; i++) {
    const promise = db
      .collection(dbName)
      .where(whereObj)
      .skip(i * MAX_LIMIT)
      .limit(MAX_LIMIT)
      .get()
    tasks.push(promise)
  }
  const rrr = await Promise.all(tasks)
  if(rrr.length ! = =0) {
    return rrr.reduce((acc, cur) = > {
      return {
        data: acc.data.concat(cur.data),
        errMsg: acc.errMsg
      }
    })
  } else {
    return {
      data: [].errMsg: "empty"}}}// Cloud function entry function
exports.main = async (event, context) => {
  const text = event.text
  const data = await getDbData("poster_users", {
    name: {
      $regex: text
    }
  })
  return data
}
Copy the code

This is a reference to the website’s recommended implementation of paging database data (because search results can be numerous), and the filter is regular fuzzy matching keywords.

Search page source path is pages/circle/search-user/search-user, the realization of click search results to jump to the corresponding item of the user’s user-data page, it is recommended to directly read the source understanding.

6. Other extensions

6.1 Poster_likes and likes

Because the principle of retweet, comment and like is basically the same, so here only describes how to write the like function, and the other two functions can be implemented by readers themselves.

Of course we need to create a new collection Poster_Likes with the following data structure for each item:

{
  "posterId": "xxx"."likeId": "xxx"
}
Copy the code

PosterId is the _id of each record in the Poster Collection, and likeId is the userId of Poster_Users.

We then extend the implementation-Poster -item:

<view class="post-item" hover-class="post-item-hover" bindlongpress="onItemLongTap" bindtap="onItemTap">.<view class="interact-area">
    <view class="interact-item">
      <button class="interact-btn" catchtap="onLikeTap" style="color:{{liked ? '#55aaff' : '#000'}}">Praise {{likeCount}}</button>
    </view>
  </view>
</view>
Copy the code

Namely, a new interact area is added, where onLikeTap is implemented as follows:

onLikeTap: function() {
  if (!this.properties.originId) return
  const that = this
  if (this.data.liked) {
    wx.showLoading({
      title: "In operation".mask: true
    })
    wx.cloud
      .callFunction({
        name: "cancelLiked".data: {
          posterId: this.properties.data._id,
          likeId: this.properties.originId
        }
      })
      .then(res= > {
        wx.showToast({
          title: "Cancelled successfully"
        })
        that.refreshLike()
        that.triggerEvent('likeEvent');
      })
      .catch(error= > {
        wx.showToast({
          title: "Cancellation failed".image: "/images/error.png"
        })
      })
      .finally(wx.hideLoading())
  } else {
    wx.showLoading({
      title: "In operation".mask: true
    })
    db.collection("poster_likes").add({
        data: {
          posterId: this.properties.data._id,
          likeId: this.properties.originId
        }
      }).then(res= > {
        wx.showToast({
          title: "Has been praised"
        })
        that.refreshLike()
        that.triggerEvent('likeEvent');
      })
      .catch(error= > {
        wx.showToast({
          title: "Like fail".image: "/images/error.png"
        })
      })
      .finally(wx.hideLoading())
  }

}
Copy the code

Careful readers will notice that this is almost the same as focusing on functional principles.

6.2 Data Refresh

We can refresh the data on the home page in many ways:

onShow: function() {
  wx.showLoading({
    title: "Loading".mask: true
  })
  const that = this
  function cb(userId) {
    that.refreshMainPageData(userId)
    that.refreshMePageData(userId)
  }
  this.getUserId(cb)
}
Copy the code

The first is to use the onShow method: it is called every time the page moves from the background to the foreground display, at which point we can refresh the page data (including Feed streams and personal information). However, user information may be lost at this point, so we need to determine in getUserId, and the refresh functions together, as a callback function.

The second is for the user to manually refresh:

onPageMainTap: function() {
  if (this.data.currentPage === "main") {
    this.refreshMainPageData()
  }
  this.setData({
    currentPage: "main"})}Copy the code

As shown in the figure, when the current page is a Feed flow, if you click the home Tab again, the data will be forcibly refreshed.

The third is a refresh triggered by associated data changes, such as dynamic type selection or deletion of a dynamic. This can be studied directly by looking at the source code.

6.3 Waiting for the first Loading

When a user enters the home page for the first time, what should we do if we want to allow the user to operate after the Feed and profile are loaded?

If it is a framework like Vue or React, we can easily think of property monitoring, such as Watch, useEffect, etc., but the Page of the applet currently does not provide property monitoring function, what should we do?

Another option in addition to implementing it yourself is to use Component observers, which are similar to the property monitoring capabilities mentioned above. Although the official website documentation is less descriptive, it can still be used to monitor.

First, create a new Component called abstractload.

// pages/circle/component/abstract-load.js
Component({
  properties: {
    pageMainLoaded: {
      type: Boolean.value: false
    },
    pageMeLoaded: {
      type: Boolean.value: false}},observers: {
    "pageMainLoaded, pageMeLoaded": function (pageMainLoaded, pageMeLoaded) {
      if (pageMainLoaded && pageMeLoaded) {
        this.triggerEvent("allLoadEvent")}}}})Copy the code

Then add a line in pages/circle/circle.wxml:

<abstract-load is="abstract-load" pageMainLoaded="{{pageMainLoaded}}" pageMeLoaded="{{pageMeLoaded}}" bind:allLoadEvent="onAllLoad" />
Copy the code

Finally, the onAllLoad function can be implemented.

In addition, it is recommended to name components starting with Abstract in your project, such as those that do not actually present data.

6.4 Bug of Scrollview in iOS

If you use iOS to debug this little program, you may find that scrolling through the Scrollview header and button will shake up and down when the Feed stream is short. This is because the WebView implemented by iOS has a rebound effect on the scroll view. This effect also triggers a scroll event.

For this bug, the official also said that there is no fix for the time being, only to endure.

6.5 About the Message Tab

Readers may be wondering why I didn’t cover the message Tab and the implementation of message alerts. First because the source code does not have this implementation, and second because I find it difficult to implement active reminders with the current capabilities provided by cloud development (there is no other way except polling).

It is hoped that future cloud development can provide the function of database long connection monitoring, so that the status of data updates can be easily obtained through the subscriber mode, and active reminders will be easier to achieve. I’ll probably update the relevant source code at that point.

6.6 Cloud Function Time

Readers may notice that I have a cloud function called Benchmark, which simply queries the database to calculate the query time.

The weird thing is that while I was debugging the day before yesterday, I found that a query takes 1 second, while at the time of writing this article it takes less than 100ms. It is recommended to set a longer timeout period for functions that require multiple database operations. Currently, the performance of cloud functions is not very stable.

7. Conclusion

So on the mini version of the micro blog development practical introduction to this end, more information can be directly downloaded source code to see oh.

Source link

Github.com/TencentClou…