【小程序开发实战】用户信息展示

568 阅读8分钟

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

前言

前面我们实现了信息流内容的展示及发布,但内容本身并没有展示对应的发布者用户头像和昵称,这一篇我们就来实现这一功能。

开放数据获取

对于用户头像和昵称这类开放信息,我们可以使用小程序提供的 开放数据组件 直接进行展示

image.png

样式修饰如下

.user-info {
  padding-bottom: 40rpx;
  display: flex;
  align-items: center;
}
.user-avatar {
  width: 80rpx;
  height: 80rpx;
  border-radius: 50%;
  border: 1rpx solid rgb(245, 243, 243);
  overflow: hidden;
}
.user-name {
  font-size: 30rpx;
  line-height: 34rpx;
  font-weight: bold;
  padding-left: 20rpx;
}

但这样只能展示当前用户自己的信息,如果是别人发布的内容,要怎么展示对应的昵称和头像呢。

用户信息管理方案

这就需要我们使用小程序提供的API来获取发布者的昵称头像,然后和其发布的内容进行关联

实现方案有很多种,比如以下两种:

方案一

我们可以在每个用户第一次进行发布时,请求用户授权TA的昵称头像,然后将其存入到我们数据库中一个单独的集合,并且为每条记录增加一个可以唯一标识某个用户的字段,比如openid,然后以此维护所有发布者的昵称和头像信息。

在后续对已授权过昵称和头像的用户发布内容时,也对内容增加用户的唯一标识openid。然后我们可以在这一步查出用户的头像和昵称,一并存入内容集合,这种做法叫数据冗余

另外,我们也可以不在内容集合中冗余存储用户的头像和昵称,而在查询内容列表时为每条记录去查找其对应的头像和昵称并附加到返回的数据中,这种方式和前面的数据冗余方式对比,就能看出数据冗余的好处。因为冗余是在每条内容新增时完成的,而不冗余则要在每次查询列表时为每一条查出来的数据再去做“二次加工”。

方案二

还有一种方案,就是在小程序侧完成用户信息的填充。我们在用户首次发布内容时,请求用户授权TA的头像和昵称,然后将其存储在小程序缓存中。然后在每次用户发布内容时,我们从缓存中读取用户信息,并将其作为参数一并传给内容发布函数。如果缓存中没有读到用户信息,则重新向用户请求授权头像和昵称。

这样做相比于方案一的好处在于,方案一是在数据库中存储了所有用户第一次发布时授权的昵称和头像,如果用户在第一次发布后修改了TA的昵称和头像,则我们在后续内容展示时都只会展示旧的昵称和头像。当然,也可以在云函数侧实现定期清除旧的用户信息,并通知小程序侧重新获取最新的用户头像和昵称,这就涉及到另一种技术方案的设计了。

而我们的方案二,由于是在小程序的缓存内存储用户信息的,也就是每个人的手机微信中存着自己的头像和昵称。所以每次当用户删除小程序,或主动清除缓存,则可以触发新一次的用户信息获取。

用户信息获取

我们采用的方案二对于用户发布的每条记录做用户信息绑定。具体实现如下

btnClick() {
  const { userInfo, lock } = this.data
  if (lock) return
  const that = this
  this.setData({
    lock: true
  })
  if (userInfo) {
    this.handleSubmit()
  } else {
    wx.getUserProfile({
      desc: '头像昵称显示',
      success: ({ userInfo }) => {
        that.storeUserInfoAndSubmit(userInfo)
      },
      fail: () => {
        wx.getUserInfo({
          success: ({ userInfo: anonymousUserInfo }) => {
            that.storeUserInfoAndSubmit(anonymousUserInfo)
          },
          fail: suberr => {
            console.log('suberr', suberr)
            that.storeUserInfoAndSubmit({
              nickName: '匿名用户',
              avatarUrl: ''
            })
          }
        })
      }
    })
  }
}

这是用户点击发布按钮后的逻辑,为了避免用户连续点击按钮导致多次触发发布逻辑,这里我们使用lock变量控制在一次发布逻辑处理完成前不会重复响应。

然后我们会在每次发布时检查当前缓存中是否有用户信息,如果有则拿着这份用户信息去进行内容发布,云函数侧也会把用户发布的内容和用户信息一并进行存储,这样后续查询出的每条内容都会带有用户昵称和头像。

如果发现缓存中没有用户信息,我们则会使用getUserProfile请求用户授权昵称和头像并进行后续操作,当然这里也不会强制用户授权,如果用户选择了拒绝,我们则会填充一个默认内容。

用户信息存储

在存储方面,分为小程序侧的缓存处理和云函数侧的内容绑定处理。

storeUserInfoAndSubmit(userInfo) {
  this.setData({
    userInfo
  }, () => {
    this.handleSubmit()
  })
  wx.setStorageSync('userInfo', userInfo)
},

这里我们使用了setStorageSync方法将获取到的用户信息存储在小程序缓存中。

image.png

这里则是云函数侧对于发布内容时传递的内容和用户信息进行绑定并存储。

个人信息展示

经过上面的改造,我们在读取内容列表时可以区分这条内容是自己发的还是别人发的,并且每条内容上都附有发布者的昵称和头像。

所以我们可以通过如下方式进行区别展示,对于自己的内容,我们仍使用开发数据进行展示自己的昵称和头像,这样可以确保对于自己的内容,永远看到的都是最新的头像和昵称。而对于别人的内容,则展示与其绑定的昵称和头像即可。

image.png

个人内容管理

既然内容和发布者的用户信息建立了关联,那我们自然要在所有的内容列表中区分出自己发布的内容进行管理。所以我们需要在小程序底部增加一个菜单切换栏,用于切换首页内容和个人信息内容。

底部菜单栏

关于底部菜单栏我们既可以通过小程序提供的方法来展示小程序自带的底部栏,也可以将其隐藏转而使用UI组件库实现的底部栏。这里我们先使用小程序自带的底部栏,相关文档

调整以后的app.json如下

{
  "pages":[
    "pages/index/index",
    "pages/add/index",
    "pages/mine/index",
    "pages/logs/logs"
  ],
  "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "",
    "navigationBarTextStyle":"black"
  },
  "tabBar": {
    "borderStyle": "white",
    "color": "#dbdbdb",
    "selectedColor": "#2c2c2c",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "assets/images/home.png",
        "selectedIconPath": "assets/images/home-selected.png"
      },
      {
        "pagePath": "pages/mine/index",
        "text": "我的",
        "iconPath": "assets/images/mine.png",
        "selectedIconPath": "assets/images/mine-selected.png"
      }
    ]
  },
  "sitemapLocation": "sitemap.json"
}

image.png

个人内容页

视图部分

<view class="{{ listData.length ? 'mine-wrap' : 'mine-wrap no-data' }}">
  <view class="user-info">
    <view class="user-avatar">
      <open-data type="userAvatarUrl"></open-data>
    </view>
    <view class="user-name">
      <open-data type="userNickName"></open-data>
    </view>
  </view>
  <view wx:if="{{ listData.length }}">
    <view>
      <content-list items="{{ listData }}"></content-list>
    </view>
    <view wx:if="{{ listData.length && noMoreData }}">
      <no-more></no-more>
    </view>
  </view>
  <view wx:if="{{ !listData.length }}" class="empty-block">
    <van-empty
      class="custom-image"
      image="https://img.yzcdn.cn/vant/custom-empty-image.png"
      description="空空如也"
    />
  </view>
</view>

样式部分

.mine-wrap {
  display: flex;
  flex-direction: column;
}
.no-data {
  height: 100vh;
}
.user-info {
  display: flex;
  align-items: center;
  padding: 40rpx;
  border-bottom: 10px solid rgb(245, 243, 243);
}
.user-avatar {
  width: 100rpx;
  height: 100rpx;
  border-radius: 50%;
  overflow: hidden;
}
.user-name {
  padding-left: 40rpx;
  font-weight: 500;
  color: #333;
  font-size: 32rpx;
}
.empty-block {
  flex: auto;
  display: flex;
  justify-content: center;
  align-items: center;
}
.van-empty__image {
  width: 120rpx !important;
  height: 120rpx !important;
}

效果

image.png

这样我们就完成的个人内容页的界面开发,接下来就是数据的读取。

数据读取

数据方面,我们依然通过内容列表的云函数进行获取,只不过这次过滤条件是将当前用户的所有内容按分页返回。

这里有一个问题,就是我们现在的内容记录其实并没有记录创建者的标识,所以我们需要为每一条记录增加用户标识信息。

小程序中有用于唯一标识一个用户的信息,叫做openid,这个值我们可以在每次云函数调用中获取,具体可参考 文档

所以我们只需要在原本用于新增数据的云函数稍加改造,将标识用户的openid加入记录即可。

image.png

然后我们在获取个人内容时,只需要在查询条件中增加openid字段用于过滤即可。

云函数改造

我们将数据获取云函数重构如下

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

  let data = []
  let totalSize = 0

  const { pageNo = 0, pageSize = 20, content = '', searchMine = false } = event

  const db = cloud.database()
  const collection = db.collection('homeContentList')
  const _ = db.command

  let searchPromise
  let countPromise

  try {
    let filtered = collection
    let condition = {}
    if (content) {
      Object.assign(condition, {
        text: db.RegExp({
          regexp: content,
          options: 'i'
        })
      })
    }
    if (searchMine) {
      Object.assign(condition, {
        openid: wxContext.OPENID
      })
    }
    if (Object.keys(condition).length) {
      filtered = collection.where(condition)
    }

    countPromise = filtered.count()
    searchPromise = filtered.skip(pageNo).limit(pageSize).get()

    const [{ data: listData }, { total }] = await Promise.all([searchPromise, countPromise])
    if (listData) {
      data = listData
    }
    totalSize = total
  } catch (error) {
    console.log('error', error)
  }

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

我们根据调用接口时的searchMine作为查询自己内容的标识,然后在查询数据库时根据这个字段是否为true来判断是否在过滤条件中增加openid为当前用户的条件。

总结

小结以下,这篇我们实现了以下功能:

  • 新增内容时增加用户唯一标识
  • 展示内容时增加用户昵称和头像
  • 增加底部菜单,包括首页、我的两栏
  • 支持查看自己发布的内容列表

在下一篇中我们将介绍如果修改或删除自己发布的内容。