最近折腾了一下微信小程序,发现小程序开发起来有它自己独特的魅力。很多常用的功能,如加载提示(wx.showLoading),都可以用一句代码实现。配合上云开发,整个开发速度得到了有力的提升。本文章,介绍如何开发一个相册小程序。
以下是小程序的GitHub链接:
https://github.com/CQJames/PhotoAlbumProgram
先看一下效果图:
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "上传照片",
"iconPath": "assets/pics1.png",
"selectedIconPath": "assets/pics2.png"
},
{
"pagePath": "pages/album/album",
"text": "我的照片墙",
"iconPath": "assets/self1.png",
"selectedIconPath": "assets/self2.png"
}
],
"selectedColor": "#3399FF"
}
//app.js
App({
onLaunch: function () {
//初始化云
wx.cloud.init({
env: "***",
traceUser: true
});
//获取openId
this.getOpenId();
// 展示本地存储能力
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
// 登录
wx.login({
success: function (res) {
}
});
// 获取用户信息
wx.getSetting({
success: res => {
if (res.authSetting['scope.userInfo']) {
// 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
wx.getUserInfo({
success: res => {
// 可以将 res 发送给后台解码出 unionId
this.globalData.userInfo = res.userInfo
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
if (this.userInfoReadyCallback) {
this.userInfoReadyCallback(res)
}
}
})
}
}
})
},
getOpenId() {
// 获取用户openid
let that = this;
wx.cloud.callFunction({
name: 'getOpenId',
complete: res => {
console.log('云函数获取到的openid: ', res.result.openId);
that.globalData.openId = res.result.openId;
}
})
},
globalData: {
userInfo: null,
openId: ""
}
})
1. 在project.config.json中添加以下代码,指定云函数根目录
"cloudfunctionRoot": "cloudFunctions/"
2. 在云函数根目录下建立名为getOpenId云函数,添加以下代码
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 云函数入口函数
exports.main = async (event, context) => {
//返回用户信息
return event.userInfo;
}
先在本地选择照片,再把照片存到云存储空间,再把图片相关信息存到云数据库。
1. 选择照片
chooseImages: function() {
//回调函数里又有新的this对象,所以必须在外部保存this(即Page对象)引用
let that = this;
wx.chooseImage({
count: 9,
success: function(chooseRes) {
//openId加生成唯一时间戳加图片索引作为图片的云路径前缀
let openId = app.globalData.openId;
//获取要上传多少张图片
let length = chooseRes.tempFilePaths.length;
wx.showLoading({
title: '上传中, 请稍后',
});
//一张一张开始上传
that.uploadImages(openId, 0, chooseRes.tempFilePaths, length);
},
fail: function(res) {
console.log("选择相片失败");
}
})
}
2. 递归实现一张照片上传完,再上传下一张
uploadImages: function (openId, index, images, length) {
let that = this;
//上传时间
let time = new Date();
let timeStamp = Date.parse(time).toString();
wx.cloud.uploadFile({
cloudPath: openId + timeStamp + index + '.png',
filePath: images[index],
success: function (res) {
console.log("上传成功");
//从上传结果中获取云端图片的路径url
let imageUrl = res.fileID;
that.setData({
imageUrl: imageUrl
});
//上传者昵称
let name = that.data.userInfo.nickName;
//添加到云数据库
that.addImageList(name, time, imageUrl);
},
fail: function (res) {
console.log("上传失败");
util.showTip('第' + (index + 1) + '张照片上传失败!');
},
complete: function(res) {
if ((index + 1) == length) {
//如果是最后一张
wx.hideLoading();
}else {
//否则,传下一张
index = index + 1;
that.uploadImages(openId, index, images, length);
}
}
});
}
3. 将图片相关信息添加至云数据库imageList集合
addImageList: function (name,time,url){
console.log(util.formatTime(time, "Y-M-D h:m:s"));
//获得数据库引用
const db = wx.cloud.database();
//把上传的图片添加到数据库
db.collection("imageList").add({
data:{
uploader: name,
time: util.formatTime(time, "Y-M-D h:m:s"),
imageUrl: url
},
success: function(res){
console.log("相片添加到数据库成功");
},
fail: function(res){
console.log("相片添加到数据库失败");
}
});
}
每次点击我的照片墙,判断云端是否有新的照片数据,有则更新照片列表。否则,不更新照片列表。
用户可以下拉刷新照片列表,上拉分页加载照片列表。
1. 每次点击我的照片墙进入onShow生命周期函数处理
onShow: function () {
let that = this;
//根据openId找属于用户的图片列表
let openId = app.globalData.openId;
const db = wx.cloud.database();
db.collection("imageList").where({ _openid: openId }).count({
success: function (res) {
console.log("获取相片列表总数成功" + res.total);
if (that.data.total != res.total) {
//如果有数据刷新,重新加载第一页
wx.pageScrollTo({
scrollTop: 0
});
that.data.pageIndex = 1;
that.fetchImageList("刷新中");
}
}
});
}
2. 下拉刷新照片列表
先在本页面json添加以下代码,以允许下拉刷新。
"enablePullDownRefresh": true
再在本页面js添加以下代码,以下下拉刷新照片列表。
onPullDownRefresh: function () {
//下拉刷新图片列表
this.data.pageIndex = 1;
this.fetchImageList("刷新中");
//停止下拉刷新
wx.stopPullDownRefresh();
}
3. 上拉分页加载照片列表
onReachBottom: function () {
if (this.data.pageIndex < this.data.pageCount) {
//如果没到最后一页,页码加1,并加载新的一页图片列表数据
this.data.pageIndex = this.data.pageIndex + 1;
this.fetchImageList("加载中");
} else {
util.showTip('没有更多照片了');
}
console.log(this.data.pageIndex)
}
fetchImageList: function (title) {
let that = this;
//根据openId找属于用户的图片列表
let openId = app.globalData.openId;
let pageIndex = that.data.pageIndex;
let pageSize = that.data.pageSize;
const db = wx.cloud.database();
//先计算总数,才可以进行分页
db.collection("imageList").where({ _openid: openId}).count({
success: function (res) {
console.log("获取相片列表总数成功" + res.total);
let pageCount = Math.ceil(res.total / pageSize);
let total = res.total;
//根据不同需求的抓取显示不同的进程提示
wx.showLoading({
title: title,
});
//分页获取图片列表内容
db.collection("imageList").where({ _openid: openId })
.skip((pageIndex - 1) * pageSize).limit(pageSize).orderBy('time', 'desc')
.get({
success: function (res) {
console.log("获取相片列表成功");
//选获取原先的图片列表
let tempImageList = that.data.dataList;
if (that.data.pageIndex == 1) {
//如果要显示第一页,无需拼接图片列表数据
tempImageList = res.data;
}else {
//否则,拼接新的图片列表数据
tempImageList = tempImageList.concat(res.data);
}
//更新数据
that.setData({
pageCount: pageCount,
total: total,
dataList: tempImageList
});
},
fail: function (res) {
console.log("获取相片列表失败");
},
complete: function (res) {
wx.hideLoading();
}
});
},
fail: function (res) {
console.log("获取相片列表总数失败");
}
});
}
点击删除,触发deleteImage事件,事件通过wxml里的data-获取该照片id和imageurl。
然后先根据id删除照片记录,再通过imageurl删除真正的照片存储。
最后要更新照片列表。为了提高用户体验,删除不刷新整个照片列表,而是采用以下策略。利用id找到要删除的照片在原来的照片列表中的位置,通过slice函数把该照片从该照片列表中去掉。然后判断云端是否还有一条或以上的照片列表数据还没加载。没有的话,直接重设该照片列表就好。如果有,需从云端加载一条新的照片数据回来,并拼接在照片列表尾部。最后,重设该照片列表。
以下是wxml:
上传者:{{item.uploader}}
上传时间:{{item.time}}
1. 删除某张照片,询问用户是否真的要删除
deleteImage: function (event) {
let that = this;
//询问用户是否删除
wx.showModal({
title: '提示',
content: '确定要删除该照片吗?',
success: function (res) {
if (res.confirm) {
//确定删除
wx.showLoading({
title: '删除中',
});
//获得图片在数据库的id
let id = event.target.dataset.id;
const db = wx.cloud.database();
//从数据库删除该记录
db.collection('imageList').doc(id).remove({
success: function (res) {
console.log("删除照片记录成功");
//获得图片在存储的fileId,先获取imageurl
let imageUrl = event.target.dataset.imageurl;
let fileId = util.getFileId(imageUrl);
//从存储真正删除该图片
wx.cloud.deleteFile({
fileList: [fileId],
success: function (res) {
console.log("删除照片成功");
},
fail: function (res) {
console.log("删除照片失败");
}
});
wx.hideLoading();
//根据id更新图片列表
that.updateImages(id);
},
fail: function (res) {
console.log("删除照片记录失败");
wx.hideLoading();
}
});
}
}
});
}
2. 根据id单独把要删除的照片从照片列表中删除,注意如何向云端请求补充当前页一条数据
updateImages: function(id) {
let that = this;
//删除后更新图片列表
let list = that.data.dataList;
let dataList = null;
//去掉删除的
for (let i = 0; i < list.length; i++) {
if (list[i]._id == id) {
let dataList1 = list.slice(0, i);
let dataList2 = list.slice(i + 1, list.length);
dataList = dataList1.concat(dataList2);
break;
}
}
//加载一张新的图片加入当前图片列表
let openId = app.globalData.openId;
let pageIndex = that.data.pageIndex;
let pageSize = that.data.pageSize;
const db = wx.cloud.database();
//更新总数,页数,还有图片列表
db.collection("imageList").where({ _openid: openId }).count({
success: function (res) {
console.log("获取相片列表总数成功" + res.total);
let pageCount = Math.ceil(res.total / pageSize);
let total = res.total;
if ((dataList.length + 1) <= res.total) {
//如果还有未加载数据,则从数据库取一条数据补充当前页
//根据不同需求的抓取显示不同的进程提示
wx.showLoading({
title: '刷新中',
});
//分页获取图片列表内容,因为是当前页补充一条新数据,
所以跳过pageIndex * pageSize - 1条
db.collection("imageList").where({ _openid: openId })
.skip(pageIndex * pageSize - 1).limit(1).orderBy('time', 'desc').get({
success: function (res) {
console.log("获取相片列表成功");
//把获得的新数据加到尾部
dataList = dataList.concat(res.data);
//更新数据
that.setData({
pageCount: pageCount,
total: total,
dataList: dataList
});
},
fail: function (res) {
console.log("获取相片列表失败");
},
complete: function (res) {
wx.hideLoading();
}
});
}else {
//没有还未加载数据
that.setData({
pageCount: pageCount,
total: total,
dataList: dataList
});
}
},
fail: function (res) {
console.log("获取相片列表总数失败");
}
});
}
------重要部分已经介绍完毕,其实上面还有许多有待改善的地方。细心的你可能已经发现,许多fail回调函数里并没有作出相应的错误处理。后来想了一下,对于一些云请求或更新,可以在fail函数里,再次发起请求或更新。当然,这可能又会引起一个新问题,就是一直失败怎么办。我觉得可以再判断失败请求的次数,如果失败次数超过3次,拒绝再重新请求。并且给用户反馈友好的失败信息。所以说,学无止境,是不是。