小程序·云开发实战 - 迷你微博
0. 前言
本文将手把手教你如何写出迷你版微博的一行行代码,迷你版微博包含以下功能:
- Feed 流:关注动态、所有动态
- 发送图文动态
- 搜索用户
- 关注系统
- 点赞动态
- 个人主页
使用到的云开发能力:
- 云数据库
- 云存储
- 云函数
- 云调用
没错,几乎是所有的云开发能力。也就是说,读完这篇实战,你就相当于完全入门了云开发!
咳咳,当然,实际上这里只是介绍核心逻辑和重点代码片段,完整代码建议下载查看。
1. 取得授权
作为一个社交平台,首先要做的肯定是经过用户授权,获取用户信息,小程序提供了很方便的接口:
这个 button
有个 open-type
属性,这个属性是专门用来使用小程序的开放能力的,而 getUserInfo
则表示 获取用户信息,可以从bindgetuserinfo
回调中获取到用户信息。
于是我们可以在 wxml 里放入这个 button
后,在相应的 js 里写如下代码:
Page({
...
getUserInfo: function(e) {
wx.navigateTo({
url: "/pages/circle/circle"
})
},
...
})
这样在成功获取到用户信息后,我们就能跳转到迷你微博页面了。
需要注意,不能使用 wx.authorize({scope: "scope.userInfo"})
来获取读取用户信息的权限,因为它不会跳出授权弹窗。目前只能使用上面所述的方式实现。
2. 主页设计
社交平台的主页大同小异,主要由三个部分组成:
- Feed 流
- 消息
- 个人信息
那么很容易就能想到这样的布局(注意新建一个 Page 哦,路径:pages/circle/circle.wxml
):
很好理解,画面主要被分为上下两个部分:上面的部分是主要内容,下面的部分是三个 Tab 组成的 Footer。重点 WXSS 实现(完整的 WXSS 可以下载源码查看):
.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;
}
核心逻辑是通过 position: fixed
来让 Footer 一直在下方。
读者会发现有一个 currentPage
的 data ,这个 data 的作用其实很直观:通过判断它的值是 main
/msg
/me
中的哪一个来决定主要内容。同时,为了让首次使用的用户知道自己在哪个 Tab,Footer 中相应的 button
也会从白底黑字黑底白字,与另外两个 Tab 形成对比。
现在我们来看看 main
部分的代码(在上面代码的基础上扩充):
...
无数据
...
这里用到了 列表渲染 和 条件渲染,还不清楚的可以点击进去学习一下。
可以看到,相比之前的代码,我添加一个 header,同时 main-area
的内部也新增了一个 scroll-view
(用于展示 Feed 流) 和一个 button
(用于编辑新迷你微博)。header 的功能很简单:左侧区域是一个 picker
,可以选择查看的动态类型(目前有 关注动态 和 所有动态 两种);右侧区域是一个按钮,点击后可以跳转到搜索页面,这两个功能我们先放一下,先继续看 main-area
的新增内容。
main-area
里的 scroll-view
是一个可监听滚动事件的列表,其中监听事件的实现:
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"
})
}
},
...
结合 wxml 可以知道,当页面向下滑动 (deltaY < 0) 时,header 和 button
会 “突然消失”,反之它们则会 “突然出现”。为了视觉上有更好地过渡,我们可以在 WXSS 中使用 transition
:
...
.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 流
3.1 post-item
前面提到,scroll-view
的内容是 Feed 流,那么首先就要想到使用 列表渲染。而且,为了方便在个人主页复用,列表渲染中的每一个 item 都要抽象出来。这时就要使用小程序中的 Custom-Component 功能了。
新建一个名为 post-item
的 Component
,其中 wxml 的实现(路径:pages/circle/component/post-item/post-item.js
):
{{data.author}}
{{data.formatDate}}
{{data.msg}}
可见,一个 poster-item
最主要有以下信息:
- 作者名
- 发送时间
- 文本内容
- 图片内容
其中,图片内容因为是可选的,所以使用了 条件渲染,这会在没有图片信息时不让图片显示区域占用屏幕空间。另外,图片内容主要是由 image-wrapper
组成,它也是一个 Custom-Component
,主要功能是:
- 强制长宽 1:1 裁剪显示图片
- 点击查看大图
- 未加载完成时显示 加载中
具体代码这里就不展示了,比较简单,读者可以在 component/image-wrapper
里找到。
回过头看 main-area
的其他新增部分,细心的读者会发现有这么一句:
无数据
这会在 Feed 流暂时没有获取到数据时给用户一个提示。
3.2 collections: poster、poster_users
展示 Feed 流的部分已经编写完毕,现在就差实际数据了。根据上一小节 poster-item
的主要信息,我们可以初步推断出一条迷你微博在 云数据库 的 collection poster
里是这样存储的:
{
"username": "Tester",
"date": "2019-07-22 12:00:00",
"text": "Ceshiwenben",
"photo": "xxx"
}
先来看 username
。由于社交平台一般不会限制用户的昵称,所以如果每条迷你微博都存储昵称,那将来每次用户修改一次昵称,就要遍历数据库把所有迷你微博项都改一遍,相当耗费时间,所以我们不如存储一个 userId
,并另外把 id 和 昵称 的对应关系存在另一个叫 poster_users
的 collection 里。
{
"userId": "xxx",
"name": "Tester",
...(其他用户信息)
}
userId
从哪里拿呢?当然是通过之前已经授权的获取用户信息接口拿到了,详细操作之后会说到。
接下来是 date
,这里最好是服务器时间(因为客户端传过来的时间可能会有误差),而云开发文档里也有提供相应的接口:serverDate。这个数据可以直接被 new Date()
使用,可以理解为一个 UTC 时间。
text
即文本信息,直接存储即可。
photo
则表示附图数据,但是限于小程序 image
元素的实现,想要显示一张图片,要么提供该图片的 url,要么提供该图片在 云存储 的 id,所以这里最佳的实践是:先把图片上传到云存储里,然后把回调里的文件 id 作为数据存储。
综上所述,最后 poster
每一项的数据结构如下:
{
"authorId": "xxx",
"date": "utc-format-date",
"text": "Ceshiwenben",
"photoId": "yyy"
}
确定数据结构后,我们就可以开始往 collection 添加数据了。但是,在此之前,我们还缺少一个重要步骤。
3.3 用户信息录入 与 云数据库
没错,我们还没有在 poster_users
里添加一条新用户的信息。这个步骤一般在 pages/circle/circle
页面首次加载时判断即可:
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: "新用户录入中"
})
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: "录入完成"
})
if (cb) cb()
}
})
.catch(err => {
wx.showToast({
title: "录入失败,请稍后重试",
image: "/images/error.png"
})
wx.navigateTo({
url: "/pages/index/index"
})
})
} else {
if (cb) cb()
}
})
}
})
} else {
wx.showToast({
title: "登陆失效,请重新授权登陆",
image: "/images/error.png"
})
wx.navigateTo({
url: "/pages/index/index"
})
}
}
})
}
代码实现比较复杂,整体思路是这样的:
- 判断是否已存储了
userId
,如果有直接返回并调用回调函数,如果没有继续 2 - 通过
wx.getSetting
获取当前设置信息 - 如果返回里有
res.authSetting["scope.userInfo"]
说明已经授权读取用户信息,继续 3,没有授权的话就跳转回首页重新授权 - 调用
wx.getUserInfo
获取用户信息,成功后提取出signature
(这是每个微信用户的唯一签名),并调用wx.setStorageSync
将其缓存 - 调用
db.collection().where().get()
,判断返回的数据是否是空数组,如果不是说明该用户已经录入(注意where()
中的筛选条件),如果是说明该用户是新用户,继续 5 - 提示新用户录入中,同时调用
db.collection().add()
来添加用户信息,最后通过回调判断是否录入成功,并提示用户
不知不觉我们就使用了云开发中的 云数据库 功能,紧接着我们就要开始使用 云存储 和 云函数了!
3.4 addPoster 与 云存储
发送新的迷你微博,需要一个编辑新迷你微博的界面,路径我定为 pages/circle/add-poster/add-poster
: