Small Program, Cloud Development, Actual Warfare - Mini Weibo

Keywords: Javascript Database Attribute SDK iOS

0. Preface

This article will teach you how to write a line of code for a mini-version of Weibo. The mini-version of Weibo contains the following functions:

  • Feed streams: Focus on dynamics, all dynamics
  • Sending Graphics and Text Dynamics
  • Search Users
  • Focus on System
  • Comment on the Trend
  • Personal Home Page

Used cloud development capabilities:

  • 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!

Cough, of course, in fact, here is only the introduction of core logic and key code fragments, complete code is recommended to download and view.

1. Acquisition of authorization

As a social platform, the first thing to do is to obtain user information through user authorization. Small programs provide a very convenient interface:

<button open-type="getUserInfo" bindgetuserinfo="getUserInfo">
  Enter the Circle
</button>

This button has an open-type attribute, which is specifically designed to use the open capabilities of the applet, while getUserInfo means to get user information, which can be obtained from the bindgetuserinfo callback.

So we can put this button in wxml and write the following code in the corresponding js:

Page({
  ...

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

  ...
})

In this way, after successfully obtaining user information, we can jump to the mini-microblog page.

It is important to note that wx. authorize ({scope: scope. userInfo}) cannot be used to gain access to user information because it does not jump out of the authorization pop-up window. At present, it can only be implemented in the way described above.

2. Home page design

The homepage of the social platform is similar to each other, mainly composed of three parts:

  • Feed stream
  • news
  • Personal information

It's easy to think of a layout like this (notice a new Page, path: 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'}}"
      >
        //news
      </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>

It's easy to understand that the picture is divided into two parts: the top part is the main content, and the bottom part is the Footer composed of three Tab s. Key WXSS implementations (complete WXSS can be downloaded for source viewing):

.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;
}

The core logic is to keep Footer down through position: fixed.

Readers will find a current Page data, which is actually very intuitive: determine the main content by judging which of its values is in main/msg/me. At the same time, in order to let users know which Tab they are using for the first time, the corresponding button in Footer will also be in white background, black background and white character, in contrast to the other two Tabs.

Now let's look at the main part of the code (extended from the above code):

...
<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 Users</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>
...

It's used here. List Rendering and conditional rendering If it's not clear, you can click in to learn.

As you can see, compared with the previous code, I added a header, and a scroll-view (to show the Feed stream) and a button (to edit the new mini-blog) were added to the main-area. Header's function is simple: the left area is a picker, you can choose to view the dynamic type (there are two kinds of dynamic and all dynamic); the right area is a button, click can jump to the search page, these two functions we put first, first continue to see the main-area of the new content.

Scroll-view in main-area is a list of scroll-view events that can listen to the implementation of the event:

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"
    })
  }
},
...

Combined with wxml, you can see that when the page slides down (deltaY < 0), the header and button will "suddenly disappear" and vice versa, they will "suddenly appear". 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.3s, margin-top 0.3s;
}
.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.3s;
  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.3s;
  background: #fff;
  z-index: 1;
}
...

3. Feed stream

3.1 post-item

As mentioned earlier, scroll-view is a Feed stream, so the first thing to think about is using List Rendering . Moreover, in order to facilitate reuse in the personal home page, each item in the list rendering should be abstracted. At this point, you need to use the small program Custom-Component It's functional.

Create a new Compponent named post-item, where the implementation of 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>

It can be seen that a poster-item has the following main information:

  • Author's Name
  • Delivery time
  • Text content
  • Picture content

Among them, the image content is used because it is optional. conditional rendering This prevents the display area from occupying screen space without image information. In addition, the image content is mainly composed of image-wrapper, which is also a Custom-Component. Its main functions are:

  • Forced Length and Width 1:1 Clipping Display Pictures
  • click to enlarge
  • Display loaded when not loaded

The specific code is not shown here. It's relatively simple. Readers can find it in component/image-wrapper.

Looking back at the other additions to main-area, careful readers will find that:

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

This will give the user a hint when the Feed stream is temporarily unavailable.

3.2 collections: poster,poster_users

The section showing the Feed stream has been written, and the actual data is now worse. Based on the main information of poster-item in the previous section, we can preliminarily infer that a mini-blog is in the Cloud database This is how it is stored in collection poster of _____________

{
  "username": "Tester",
  "date": "2019-07-22 12:00:00",
  "text": "Ceshiwenben",
  "photo": "xxx"
}

Let's start with username. Since social platforms generally do not restrict users'nicknames, if every mini-blog stores nicknames, then every time a user changes a nickname in the future, it will take a lot of time to traverse the database and change all mini-blog entries. So we might as well store a userId, and also pair id and nickname. The relationship should exist in another collection called poster_users.

{
  "userId": "xxx",
  "name": "Tester",
  (Other User Information)
}

Where does userId come from? Of course, it is obtained through the user information interface which has been authorized before, and it will be mentioned after detailed operation.

Next comes date, which is best servers time (because there may be errors in the time passed by the client), and cloud development documents also provide corresponding interfaces: serverDate . This data can be directly used by new Date(), which can be understood as a UTC time.

Text is text information, which can be stored directly.

photo represents the attached image data, but is limited to the implementation of the image element in the widget. If you want to display a picture, either provide the url of the picture, or provide the picture in the Cloud storage The best practice here is to upload the image to the cloud storage and then store the file id in the callback as data.

In summary, the data structure of each item of poster is as follows:

{
  "authorId": "xxx",
  "date": "utc-format-date",
  "text": "Ceshiwenben",
  "photoId": "yyy"
}

Once we have determined the data structure, we can start adding data to the collection. But before that, we still lack an important step.

3.3 User Information Entry and Cloud Database

Yes, we haven't added a new user's information to poster_users yet. This step is usually judged when pages/circle/circle pages are first loaded:

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 Input"
                  })
                  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 Completion"
                        })
                        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: "The landing is invalid. Please re-authorize the landing.",
          image: "/images/error.png"
        })
        wx.navigateTo({
          url: "/pages/index/index"
        })
      }
    }
  })
}

Code implementation is more complex, the overall idea is as follows:

  1. Determine if userId has been stored, if it returns directly and calls the callback function, if it does not continue 2
  2. Get the current settings information through wx.getSetting
  3. If there is res.authSetting["scope.userInfo"] in the return indicating that the user information has been authorized to read, continue 3, and jump back to the home page to re-authorize if there is no authorization.
  4. Call wx.getUserInfo to get user information, extract signature after success (this is the only signature for each microcredit user), and call wx.setStorageSync to cache it.
  5. Call db.collection().where().get() to determine whether the returned data is an empty array, if it does not indicate that the user has entered (note the filter conditions in where(), if it means that the user is a new user, continue with 5
  6. Prompt new users to enter, and call db.collection().add() to add user information. Finally, through callbacks to determine whether the entry is successful, and prompt users

We unconsciously use the cloud database function in cloud development, and then we will start using cloud storage and cloud functions!

3.4 addPoster and Cloud Storage

Sending new mini-blogs requires an interface for editing new mini-blogs. The path I set is 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 Picture Upload" />
      </view>
    </view>
  </view>
  <view class="footer">
    <button class="footer-btn" bindtap="onSendTap">Send out</button>
  </view>
</view>

The code for wxml is well understood: textarea displays edited text, image-wrapper displays images that need to be uploaded, and at the bottom is a button sent. Among them, the bindtap event of 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]
      })
    }
  })
}

Get the temporary path of the local image directly through the official API of wx.chooseImage. When the send button is clicked, the following code will be executed:

onSendTap: function() {
  if (this.data.text === "" && this.data.imageSrc === "") {
    wx.showModal({
      title: "error",
      content: "Cannot send empty content",
      showCancel: false,
      confirmText: "Well"
    })
    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: "Successful delivery"
      })
      wx.navigateBack({
        delta: 1
      })
    })
    .catch(error => {
      that.onSendFail()
    })
    .finally(wx.hideLoading())
}
  1. First, determine whether the text and image contents are empty, if they are not sent, if they are not continued 2
  2. In sending, upload pictures to cloud storage. Note that some special characters of temporary url in pictures need to be replaced. The reasons are as follows. Naming restrictions for file names
  3. After successful upload, call db.collection().add(), send success back to the previous page (that is, the home page), if failed, the onSendFail function is executed, the latter see the source code, the logic is simpler, not to mention here.

So we created the first mini-blog in this way. Next let it be displayed in the Feed stream!

3.5 Cloud function getMainPageData

The main function of this function is to return the final data to the client by processing the data in the cloud database, which visualizes the data to the user. Let's make a preliminary version first, because there is only one data in poster_users, so we only show our mini-blog first. The getMainPageData cloud function code 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) => {
  // Obtain entry parameters through event
  const userId = event.userId
  let followingResult
  let users
  // idNameMap is responsible for storing the mapping relationship between userId and name
  let idNameMap = {}
  let followingIds = []
  // Getting 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
  })
  // Getting Dynamic
  const postResult = await db
    .collection("poster")
    .orderBy("date", "desc")
    .where({
      // Screening qualified userId through advanced screening function
      authorId: db.command.in(followingIds)
    })
    .get()
  const postData = postResult.data
  // Add the author attribute that stores the user's nickname and the formatDate attribute that stores the formatted time to the returned data
  postData.map(p => {
    p.author = idNameMap[p.authorId]
    p.formatDate = new Date(p.date).toLocaleDateString("zh-Hans", options)
  })
  return postData
}

Finally, add cloud calls in 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: "Acquisition dynamic failure",
        image: "/images/error.png"
      })
      wx.hideLoading()
    })
}

Feed stream data can be displayed to users.

Later, getMainPageData will add the function of querying all user dynamics and focusing on user dynamics according to different usage scenarios, but the principle is the same. Seeing the source code can be easily understood, and it will not be explained later.

4. System Concern

In the last section, we used most of the main functions of cloud development: cloud database, cloud storage, cloud functions, cloud calls, and then other functions are basically dependent on them.

4.1 poster_user_follows

First, we need to build a new collection poster_user_follows, in which the data structure of each item is as follows:

{
  "followerId": "xxx",
  "followingId": "xxx"
}

Simply, followerId means the person concerned, and followingId means the person concerned.

4.2 user-data page

Pay attention or cancel attention to the need to enter other people's personal home page operation, we put a user-info custom component in pages/circle/user-data/user-data.wxml, and then create a new component editor:

<view class="user-info">
  <view class="info-item" hover-class="info-item-hover">User name: {{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">Number of concerns: {{followingCount}}</view>
  <view class="info-item" hover-class="info-item-hover" bindtap="onFollowerCountTap">Number of fans: {{followerCount}}</view>
  <view class="info-item" hover-class="info-item-hover" wx:if="{{originId && originId !== '' && originId !== userId}}"><button bindtap="onFollowTap">{{followText}}</button></view>
</view>

Note the conditional rendering button here: If the value of the user id (originId) currently accessing the personal home page is the same as the value of the user id (userId) being accessed, the button will not be rendered (you can't pay attention to yourself/cancel paying attention to yourself).

We will focus on the implementation of onFollowTap:

onFollowTap: function() {
  const that = this
  // Judging the current state of concern
  if (this.data.isFollow) {
    wx.showLoading({
      title: "Operation",
      mask: true
    })
    wx.cloud
      .callFunction({
        name: "cancelFollowing",
        data: {
          followerId: this.properties.originId,
          followingId: this.properties.userId
        }
      })
      .then(res => {
        wx.showToast({
          title: "Cancel Focus on Success"
        })
        that.setData({
          isFollow: false,
          followText: "follow"
        })
      })
      .catch(error => {
        wx.showToast({
          title: "Failure to cancel attention",
          image: "/images/error.png"
        })
      })
      .finally(wx.hideLoading())
  } else if (this.data.isFollow !== undefined) {
    wx.showLoading({
      title: "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: "Remove attention"
        })
      })
      .catch(error => {
        wx.showToast({
          title: "Focus on failure",
          image: "/images/error.png"
        })
      })
      .finally(wx.hideLoading())
    }
  }
}

Here readers may wonder: why call db.collection().add() directly when you pay attention, and call cloud functions when you cancel attention? This involves the design of cloud databases: deleting multiple data operations, or deleting data filtered using where, can only be performed on the server side. If you really want to delete it on the client side, when querying the user relationship, save the _id of the unique identifying data with setData, and then delete it with db.collection().doc(_id).delete(). Readers can choose between these two ways. Of course, another implementation is not to actually delete data, just add an isDelete field to mark it.

The implementation of query user relationship is very simple. Cloud function is implemented in the following way:

// 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
}

The client only needs to check whether the length of the data returned is greater than 0.

In addition, we attach the implementation of the cloud function for obtaining other data from user-data page:

// 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
  const tasks = []
  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
}

It's easy to understand that the client can use it directly after getting it back.

5. Search Pages

This part is actually very good to achieve. The key search functions are 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
}

This paper refers to the implementation of paging database data retrieval recommended by the official website (because there may be many search results), and the filtering condition is regular fuzzy matching keywords.

The source code path of the search page is pages/circle/search-user/search-user. It realizes clicking the search result item to jump to the user-data page of the corresponding user. It is recommended to read the source code directly.

6. Other extensions

6.1 poster_likes and praise

Since the principles of forwarding, commenting and commenting are basically the same, this article only introduces how to compile the function of commenting. The other two functions can be realized by the readers themselves.

There is no doubt that we need to create a collection poster_likes, each of which has the following data structure:

{
  "posterId": "xxx",
  "likeId": "xxx"
}

The posterId here is the _id value of each record in the poster collection, and likeId is the userId in the poster_users.

Then we extend the implementation of 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'} "> Zan {like Count} </button>"
    </view>
  </view>
</view>

That is, add an interaction-area, where onLikeTap is implemented as follows:

onLikeTap: function() {
  if (!this.properties.originId) return
  const that = this
  if (this.data.liked) {
    wx.showLoading({
      title: "Operation",
      mask: true
    })
    wx.cloud
      .callFunction({
        name: "cancelLiked",
        data: {
          posterId: this.properties.data._id,
          likeId: this.properties.originId
        }
      })
      .then(res => {
        wx.showToast({
          title: "Cancellation Successful"
        })
        that.refreshLike()
        that.triggerEvent('likeEvent');
      })
      .catch(error => {
        wx.showToast({
          title: "Cancellation failure",
          image: "/images/error.png"
        })
      })
      .finally(wx.hideLoading())
  } else {
    wx.showLoading({
      title: "Operation",
      mask: true
    })
    db.collection("poster_likes").add({
        data: {
          posterId: this.properties.data._id,
          likeId: this.properties.originId
        }
      }).then(res => {
        wx.showToast({
          title: "Praised"
        })
        that.refreshLike()
        that.triggerEvent('likeEvent');
      })
      .catch(error => {
        wx.showToast({
          title: "Praise failure",
          image: "/images/error.png"
        })
      })
      .finally(wx.hideLoading())
  }

}

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

6.2 Data refresh

There are many ways to refresh data on the home page:

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

The first is to use the onShow method: it will be invoked every time the page moves from the background to the front stage display, at which time we can refresh the page data (including Feed flow and personal information). But user information may be lost at this time, so we need to judge in getUserId and integrate the functions that refresh the data as callback functions.

The second is to let users refresh manually:

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

As shown in the figure, when the current page is a Feed stream, if you click on the home page Tab again, the data will be forced to refresh.

The third one is the refresh triggered by association data changes, such as dynamic type selection, deletion of a dynamic trigger data refresh. This kind of learning can directly look at the source code.

6.3 First Load Waiting

When the user first enters the main page, how should we implement it if we want to load the Feed stream and personal information before allowing the user to operate?

If it's a framework like Vue or React, it's easy to think of attribute monitoring, such as watch, use Effect, etc. But the small program does not provide attribute monitoring function at present. What should we do?

In addition to their own implementation, there is another way to use Component observers, which is similar to the attribute monitoring function mentioned above. Although there are few official documents describing it, it can still be used for monitoring after some exploration.

First, we will create a new Component called abstract-load, which is implemented as follows:

// 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")
      }
    }
  }
})

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

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

Finally, the onAllLoad function can be implemented.

In addition, Component s like this, which do not actually display data, are recommended to be named at the beginning of abstract in all projects.

6.4 scroll-view bug in iOS

If the reader debugs this small program using iOS system, he may find that scroll-view header and button will jitter up and down when the Feed flow is short. This is because the WebView implemented by iOS itself has a rebound effect on the scroll view, and this effect also triggers the scroll event.

For this bug, Officials also said it was temporarily impossible to repair it. I can only endure it first.

6.5 About Message Tab

Readers may wonder why I didn't explain message Tab and the implementation of message reminders. First of all, because the source code does not achieve this, and secondly, I think the ability provided by cloud development to achieve active reminder is more troublesome (except polling can not think of other ways).

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 subscriber mode, and active reminder can be more easily realized. Then I may update the source code again.

6.6 On Cloud Function Time-consuming

Readers may find that I have a cloud function called benchmark, which is just an operation to query the database in order to compute the query time-consuming.

Strangely, when I debugged the day before yesterday, I found that it took 1 second to query once, while writing this article was less than 100 ms. It is suggested that in some function configurations that require multiple operations of the database, set the timeout a little longer. At present, the performance of cloud function is not stable.

7. Concluding remarks

So that's the end of the practical introduction of mini-version microblog development. More information can be downloaded directly to view the source code.

Source Link

https://github.com/TencentClo...

If you want to share your technical stories / experience in technology warfare related to CloudBase development in the cloud, please leave a message to contact us.

Posted by sONOCOOLO on Tue, 23 Jul 2019 21:18:38 -0700