源码地址:https://github.com/Vince666-ming/weapp-timeTable
这是我第一次独立完成一个项目,从学习前端知识,到搭建自己的域名服务器,这期间我成长了许多许多。起初开发的目的不仅是为了学习前端和小程序开发,更多的是想要为自己量身做一款实用的小程序,帮助自己管理日程。其实开发的过程并不顺利,因为开发时候距离小程序上线有差不多两年时间,相关书籍、视频和博客并不多,且教程基本都是仅带有前端的,遇到过很多难关都只能通过耗费大量时间查找资料进行攻克。
关于小程序的后端,起初是打算用node.js和mysql数据库的,事实上自己也学习一部分,最后发现通过小程序自带的云函数,云数据库和云存储就可以将这些需求都实现,于是最终选择了云开发。
小程序云开发介绍
我使用小程序云开发实现了获取时间表、上传时间表、分享动态、点赞、评论、收藏等功能,下文将详细介绍。
这里是微信开发文档对微信云开发的介绍。
数据库
云开发提供了一个 JSON 数据库,顾名思义,数据库中的每条记录都是一个 JSON 格式的对象。一个数据库可以有多个集合(相当于关系型数据中的表),集合可看做一个 JSON 数组,数组中的每个对象就是一条记录,记录的格式是 JSON 对象。
关系型--------------------文档型
数据库 database---------数据库 database
表 table----------------集合 collection
行 row------------------记录 record / doc
列 column---------------字段 field
存储
云开发提供了一块存储空间,提供了上传文件到云端、带权限管理的云端下载能力,开发者可以在小程序端和云函数端通过 API 使用云存储功能。
在小程序端可以分别调用 wx.cloud.uploadFile 和 wx.cloud.downloadFile 完成上传和下载云文件操作。
云函数
云函数是一段运行在云端的代码,无需管理服务器,在开发工具内编写、一键上传部署即可运行后端代码。
小程序内提供了专门用于云函数调用的 API。开发者可以在云函数内使用 wx-server-sdk 提供的 getWXContext 方法获取到每次调用的上下文(appid、openid 等),无需维护复杂的鉴权机制,即可获取天然可信任的用户登录态(openid)。
这里是我的小程序后台截图。
![2020-2-16 160433.png](https://blog.vince666.cn/usr/uploads/2020/02/3184383104.png)
![2020-2-16 160728.png](https://blog.vince666.cn/usr/uploads/2020/02/1211876716.png)
具体的调用我将在后面具体说明。
代码优化,组件化开发等
组件化开发对开发效率有很大帮助,提高代码可读性和复用性。首先先开一个components文件夹,将所有组件都写在里面,每个组件是一个文件夹,里面有wxml,js,json,css文件,调用时候只需要在调用文件夹中json中加入自定义名字和组件路径即可,如
{
"usingComponents": {
"v-dayIndex":"/components/dayIndex/dayIndex",
"van-tab": "vant-weapp/tab",
}
}
然后在wxml中使用自定义名字即可,如<v-dayIndex/>
小程序开发中目录的分配和文件的起名也很重要,主要目的是为了增加可读性,我的小程序主要目录是pages中存主要界面,functions和meFunctions(“我的”页面中功能较多,专门设了一个文件夹)中存次要页面,components存组件。这方面没有刻意的去阅读代码优化,看上去有些乱,因此打算找个时间好好阅读相关书籍,寻找前辈指教。
还有一点是我后来才知道,写代码要勤加注释,增强可读性,以后做项目要仔细写注释。
小程序赋值方法是:
this.setData({
content:e.detail
});
其中content作为标识符要在data中定义,然后才能在wxml中使用。这个非常基础,且非常重要,以下不在多说。
小程序主体开发
小程序最基本的功能在app.json中配置,其中有三个主要代码块,包括pages,window,tabBar.
pages中写小程序的主要页面,我的主要页面有“首页”、“创建”、“分享”、“模板”、“我的”,其他页面例如历史时间表、获取天气等小得页面中可以不加入到其中。需要注意的是,在开发过程中写特定页面时候,可以添加新的编译模式,来设置启动路径,这样一点击编译就可以直接跳转到这个页面,会方便很多。然而想要通过启动路径跳转就必须将该页路径加到pages中,开发完毕可以删去。
window中全局设置小程序顶部的字和背景颜色等界面信息,其他界面想拥有特别的可以在各自的json中通过navigationBarTitleText设置。
tabBar设置小程序下方的选项卡,包括字、图标(点击前后),以及点击时候索引到页面的路径pagePath.
附一个开发界面。
![2020-2-16 170548.png](https://blog.vince666.cn/usr/uploads/2020/02/1989542690.png)
生命周期函数
页面生命周期
onLoad 监听页面加载,一个页面只会调用一次
onShow 监听页面显示,每次打开页面都会调用
onReady 监听页面初次渲染完成,一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互
onHide 监听页面隐藏
onUnload 监听页面卸载
组件生命周期
created 在组件实例刚刚被创建时执行
attached 在组件实例进入页面节点树时执行
ready 在组件在视图层布局完成后执行
moved 在组件实例被移动到节点树另一个位置时执行
detached 在组件实例被从页面节点树移除时执行
最常用的是onLoad和created,用于动态显示,即一进入这个页面就自动会加载这个函数中的内容。
onShow和attached也很常用,比如在好友动态列表中会有一个发表动态按钮,发表动态后返回这个页面,这个页面要自动刷新一次,监听页面显示,每次打开页面都会调用。
通常在生命周期函数第一行加一个showLoading和hideLoading,显示加载中,加载完成隐藏加载。
列表渲染
列表渲染非常常用,比如获取到数据库内容,从而加载图片、加载动态、加载七日天气、加载评论,当然不能一条一条写在wxml上,要通过列表渲染,简化代码量。
列表渲染形式是wx:for="{{list}}" wx:key="key",其中list是时间表数组,首先在data中定义这个数组,然后在onLoad或者created生命周期函数中申请数据库内容,success或then后this.setData将res.data赋值给list数组,这样list数组就存储了获取到数据库的内容,包含wx:for的view中嵌套的就是内次循环的值,这时list写成item进行引用数据库内容,例如item.day。
很明显,列表渲染类似于for循环,for循环就有下标,想要获取下标可以通过id="{{index}}"传给js的bindtap中。
这是我列表渲染七日天气的代码:
<view class="week">
<view class="title">{{weather.city}}七日天气</view>
<block wx:for='{{weather.data}}' wx:key='key_list'>
<view>{{item.day}} - {{item.wea}} - {{item.tem1}}/{{item.tem2}}</view>
</block>
</view>
条件渲染
条件渲染也很实用,有时可能不是很必要,但对一个优秀的项目来说是必不可少的,对用户交互会很有帮助。
比如用户查看自己发布过的动态,查看自己的收藏,查看浏览记录等等,例如收藏内容是空,则显示“收藏列表空空如也”,如果不为空,则显示收藏内容。例如点赞数小于1k则显示实际数字,大于1k则显示1k+。例如动态列表如果评论数小于等于两个则直接在动态列表,如果大于两个则在动态下方加一条文本“点击查看n条评论”,同时加上跳转到详情页的navigator.
调用方法是wx:if和wx:else,类似于条件判断,以下为获取评论的代码:
<view>{{item.colComment[0]}}</view>
<view>{{item.colComment[1]}}</view>
<view wx:if="{{item.colComment.length>0}}" class="goToComment" bindtap='goToComment' data-id="{{item._id}}">查看全部{{item.colComment.length}}评论</view>
<view wx:else class="goToComment" bindtap='goToComment' data-id="{{item._id}}">暂无评论,发句友善的评论吧~</view>
注意最外层是列表渲染动态,所以是item,进入评论页面需要传值到目标页面的生命周期函数中,因为要确保页面是动态的,从而点每个动态进入的都是该动态,而不是其他动态。
界面设计
因为小程序自带的按钮、页签等不是很个性美观,我这里使用了vant有赞UI,官网是Vant 4 - A lightweight, customizable Vue UI library for mobile web apps.
使用需要进行根据官网指令通过npm安装第三包,使用时候在各个页面的json中加入路径即可,如
{
"usingComponents": {
"v-dayIndex":"/components/dayIndex/dayIndex",
"van-tab": "vant-weapp/tab",
}
}
我主页的页签切换,计划表表格界面,时间滚轮,百叶窗式查看计划表和一些按钮使用了vant组件库。有一个坑是通过npm安装后要在微信开发工具中构建npm.
页面跳转
有时候需要点击一个图标、一个按钮、或者一段文字实现页面的跳转,此时有两种方法,一种是在wxml中<navigator url="路径"><view/></navigator>,一种是<view>或<image>中加入bindtap点击事件,在js中这个方法中写入wx.navigatorTo函数,例如:
weather: function(e) {
var id = e.currentTarget.dataset.id;
wx.navigateTo({
url: '/pages/details/details?id=' + id
})
}
也可以navigatorBack,但这样跳转到目标页面后没有返回按钮,即不加入页面栈。
有时候点击事件需要传值,需要用data-id,例如列表渲染历史时间表,如何确保点击后进入的是特定对应的时间表,以确保页面是动态的,就需要传值,格式data-id="{{item._id}}",然后在js中对方法进行实现,参数就是传进去的值,开发过程中可以在函数中console.log(e);查看传进的值。注意button用的是data-id,<view>用的是id。
在跳转之后的页面,生命周期函数中的参数就是传进去的值。
提交表单,获取文本框内容
提交表单用途广泛,例如发布动态,创建时间表,评论。
首先,表单内容和按钮嵌套在<form></form>中。然后在各个文本框中加入name={{name}}使用button提交表单时候可以省去点击事件和传值,只需要加一句 form-type="submit" ,在js中实现时候函数名即为submit,参数即为表单中数据,所有文本框内容都以name和value的键值对形式被封装在e.detail.value中,接下来可以将他们的值上传给数据库。
注意:表单内容和按钮一定都要包在<form></form>中。
提交表单还有一种方法是文本框中加bind:change函数,在js中实现时候函数参数e.detail即为自动读取到的文本内容,将其赋值给标识符content,然后在提交表单按钮bindtap中通过this.data.content即可间接获取到文本框输入内容。评分也是同理。
清空表单
清空表单只需要为每个文本框设置 value="{{clear}}" ,在清空按钮的点击事件中设置clear='';
小程序实现页签切换,轮播图
小程序页签切换和轮播图如出一辙,都是使用swiper和swiper-item实现的。
轮播图是wxml页面列表渲染图片url数组,将每个图点击事件传入id={{index}},然后在js中每个判断id匹配navigator地址,即可完成跳转。
当然,这是因为轮播图有限的条件下,如果数据量大的话就可以渲染数据库申请的内容。
![PQE49ZFMLP_@@(958H`V%T5.png](https://blog.vince666.cn/usr/uploads/2020/02/3128903548.png)
页签切换我使用的是vant组件库,调用方法和微信自带的大同小异,附上源码:
wxml
<van-dialog id="van-dialog" />
<van-tabs animated swipeable line-width="80" bind:click="onClick">
<van-tab title="日计划表">
<v-dayIndex />
</van-tab>
<van-tab title="周末计划表">
<v-moIndex/>
</van-tab>
<van-tab title="周计划表">
<v-weekIndex/>
</van-tab>
</van-tabs>
//index.js
import Dialog from '../../miniprogram_npm/vant-weapp/dialog/dialog';
Page({
data: {
},
// 页签切换
onClick(event) {
wx.showToast({
title: `计划表 ${event.detail.index + 1}`,
icon: 'none'
});
}
})
![d](https://blog.vince666.cn/usr/uploads/2020/02/3277913523.png)
调用api
小程序中调用了天气api网站的api,以下为获取的天气信息:
![CC5%I4GV2W8J349M64{UPHJ.png](https://blog.vince666.cn/usr/uploads/2020/02/3559872048.png)
第一步当然是选择一个提供天气api的网站进行注册,获取APPID和APPSecret。
第二步是调用https://ip.tianqiapi.com获取城市ip地址。
第三步然后根据ip地址调用https://www.tianqiapi.com/api/?version=v1&appid=example&appsecret=example,获取到天气信息,并传入一个自定义数组(叫容器应该更贴切)中。
第四步是在wxml中通过双层大括号显示请求到的数据。
<!--index.wxml-->
<view class='container'>
<view class="today">
<view class="title">{{weather.city}}实况天气预报</view>
<view>气象台 {{weather.update_time}} 更新</view>
<view>{{weather.data[0].tem}}℃ {{weather.data[0].wea}}</view>
<view>{{weather.data[0].win[0]}} {{weather.data[0].win_speed}} </view>
<view>紫外线指数: {{weather.data[0].index[0].level}}</view>
<view>穿衣指数: {{weather.data[0].index[3].level}}</view>
<view>洗车指数: {{weather.data[0].index[4].level}}hPa</view>
<view>空气质量 {{weather.data[0].index[5].level}}</view>
<view>友情提示:{{weather.data[0].index[1].desc}}</view>
</view>
<view class="hr"></view>
<view class="week">
<view class="title">{{weather.city}}七日天气</view>
<block wx:for='{{weather.data}}' wx:key='key_list'>
<view>{{item.day}} - {{item.wea}} - {{item.tem1}}/{{item.tem2}}</view>
</block>
</view>
<view class="hr"></view>
</view>
// components/nowWeather/nowWeather.js
Component({
/**
* 组件的属性列表
*/
/**
* 组件的初始数据
*/
data: {
// location: [],
weather: []
},
/**
* 组件的方法列表
*/
created: function (options) {
this.getapi();
// this.weatherweekday();
},
methods: {
getapi: function () {
var _this = this;
// 获取IP地址
wx.request({
url: 'https://ip.tianqiapi.com/',
data: {},
method: 'GET',
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: function (res) {
console.log(res);
// 根据IP获取天气数据
_this.setData({
location: res.data
});
_this.weathertoday(res.data.ip);
// _this.weatherweekday(res.data.ip)
}
});
},
// 天气api实况天气
weathertoday: function (ip) {
var _this = this;
wx.request({
url: 'https://www.tianqiapi.com/api/?version=v1&appid=77118166&appsecret=wQ7NdnTF',
data: {
'ip': ip
},
method: 'GET',
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: function (res) {
_this.setData({
weather: res.data
});
console.log(res.data)
}
});
}
}
})
调用api会用到操作系统http的内容,此处不在累赘。
窗口交互
在加载图片、记录等内容时候,可以在js函数中申请数据库前加一句wx.showLoading({title: '加载中...',}),在success或then时候wx.hideLoading(),在error或catch时候
wx.hideLoading()
wx.showToast({
title: '网络连接不稳定,请检查网络连接',
icon: 'none',
duration: 2000
})
会得到不错的效果。
保存图片
保存图片有两种方法,一种是预览图片,在预览界面长按图片可以选择保存和收藏,另一种方法是调用wx.cloud.getTempFileURL和wx.downloadFile和wx.saveImageToPhotosAlbum,还有获取相册权限wx.openSetting。第一种方法将在下面细说,这里着重讲第二种方法。
第一步,在wxml中bindtap传值文件地址,因为曾经上传时候会将突破保存到云存储特定目录,同时数据库会留一个字段专门保存fileIds,不然渲染时候就找不到图片了。
第二步,js中点击事件函数接收到文件地址,e.currentTarget.dataset即为云文件 ID.
第三步,调用 wx.cloud.getTempFileURL方法,云文件 ID赋值给fileList,用云文件 ID 换取真实链接,将真实地址记录。
第四步,调用 wx.downloadFile申请下载,url赋值真实URL,showToast或showModel提示成功失败。(用上showLoading和hideLoading会更好)
第五步,调用wx.saveImageToPhotosAlbum方法将图片保存到用户相册,res.tempFilePath赋值给filePath,保存成功。
第六步,如果保存失败,判断失败原因err.errMsg是不是因为相册授权错误,如果是申请授权,如果不是则showToast网络连接错误.
第七步,因为微信做过调整,授权必须要在按钮中触发,因此需要在弹框回调showModal中进行调用而不是最开始就调用,询问是否授权相册, 是则调用wx.openSetting方法授权,成功后会再次自动下载。
附上代码:
saveImg: function(e) {
var that = this;
// console.log(e);
console.log("fileID是:" + e.currentTarget.dataset.id[0]);
wx.cloud.getTempFileURL({
fileList: [e.currentTarget.dataset.id[0]],
success: res => {
// get temp file URL
console.log("文件url地址:" + res.fileList[0].tempFileURL)
this.setData({
imgUrl: res.fileList[0].tempFileURL
})
wx.showLoading({
title: '保存中...'
})
wx.downloadFile({
url: this.data.imgUrl,
success: function(res) {
//图片保存到本地
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: function(data) {
wx.hideLoading()
wx.showModal({
title: '提示',
content: ' 保存成功!',
showCancel: false,
})
},
fail: function(err) {
if (err.errMsg === "saveImageToPhotosAlbum:fail:auth denied" || err.errMsg === "saveImageToPhotosAlbum:fail auth deny") {
// 这边微信做过调整,必须要在按钮中触发,因此需要在弹框回调中进行调用
wx.showModal({
title: '提示',
content: '需要您授权保存相册',
showCancel: false,
success: modalSuccess => {
wx.openSetting({
success(settingdata) {
console.log("settingdata", settingdata)
if (settingdata.authSetting['scope.writePhotosAlbum']) {
wx.showModal({
title: '提示',
content: '获取权限成功,再次点击图片即可保存',
showCancel: false,
})
} else {
wx.showModal({
title: '提示',
content: '获取权限失败,将无法保存到相册哦~',
showCancel: false,
})
}
},
fail(failData) {
console.log("failData", failData)
},
complete(finishData) {
console.log("finishData", finishData)
}
})
}
})
}
},
complete(res) {
wx.hideLoading()
}
})
}
})
},
fail: err => {
console.log(error)
// handle error
}
})
},
预览图片
预览图片简单的多,传值图片id给js的监听点击函数中,调用wx.previewImage方法给URL复制即可。
previewImg:function(e){
console.log(e)
wx.previewImage({
current: e.currentTarget.dataset.id, // 当前显示图片的http链接
urls: e.currentTarget.dataset.id // 需要预览的图片http链接列表
})
},
复制和一键复制
在<text>中加入selectable='true'则表示字体可以被用户手动复制。
<text class="words" selectable='true'>{{item.content}}</text>
一键复制需写bindtap函数,将复制内容通过data-id传值给js中进行实现。调用wx.setClipboardData方法给data复制 e.currentTarget.dataset.id即可。
<button class="save2" type="default" bindtap='saveDoc' data-id="{{item.content}}">保存文档</button>
saveDoc: function(e) {
console.log(e.currentTarget.dataset.id)
wx.setClipboardData({
//准备复制的数据
data: e.currentTarget.dataset.id,
success: function(res) {
wx.hideToast();
Dialog.alert({
title: '下载链接已复制到剪贴板!',
message: '将复制的链接粘贴至浏览器即可开始下载'
})
}
})
},
云开发详解
这方面其实微信开发文档已经介绍的相当详细。应用则非常广泛,例如表单数据的上传和获取,和云存储结合上传文件,点赞、收藏、评论等等,功能很强大。
云数据库每个集合可以在后台设置读写权限,包括仅创建者和所有用户皆可读或者写。
通过云数据库获取数据一次最多20,通过云函数获取数据一次最多100,更新、删除多条数据必须通过云函数操作数据库。
首先,创建、查看、删除时间表功能块是通过云开发完成的。
使用数据库首先要const db = wx.cloud.database();获取默认环境的数据库的引用。
//增
db.collection('todos').add({
// data 字段表示需新增的 JSON 数据
data: {
description: "learn cloud database", //在todos集合中新建一条记录,
location: new db.Geo.Point(113, 23) //description和location将是记录中的字段。
}
})
.then(res => {
console.log(res) //成功或失败后后可以加showModal或showToast提示框,提高用户交互
}).catch(err => {
error //可以在申请前一行加showLoading,成功或失败第一时间hideLoading.
})
有一点需要注意的是,每次获取数据库内容有限制,其实是有益处的,对于用户来说每次直接调用大量数据难免会出现加载缓慢,对后台也是负担,解决办法就是用skip方法来进行分页获取,每页最高20个记录,这样效率会高很多,当然都有特殊情况,可能获取的记录内容都较为单一,例如每个记录只有一两个字,这样每页20个会少,可以通过云函数每次获取最高100条内容,以下将对skip分页和云函数获取数据库记录讲解:
skip分页获取数据库内容
skip是指定查询返回结果时从指定序列后的结果开始返回,常用于分页。使用分页时还常用到pageScrollTo函数,将页面滚动到目标位置。因为在浏览到页面最低端时候,点击下一页,页面会自动显示第二页的底部,这时要通过这个函数让页面自动滚到页首scrollTop: 0.
我实现分页的方法是通过定义一个变量numSkip,用它来记录跳转的次数,从而可以判断出首页,不出现bug。
skip是获取数据库时候使用的,意思是从第多少条获取,配合limit获得获取数量,同时每次调用要判断numSkip的值。
附上源码:
next: function () { //下一页
var num = this.data.numSkip
num = num + 5;
wx.showLoading({
title: '加载中...',
})
db.collection('shareData').orderBy('id', 'desc').skip(num).limit(5).get().then(res => {
wx.hideLoading();
if (res.data.length != 0) {
console.log(res);
this.setData({
list: res.data,
})
wx.pageScrollTo({
scrollTop: 0
})
} else {
num = num - 5
wx.showToast({
icon: "none",
title: '暂无更多内容~',
})
}
this.setData({
numSkip: num
})
}).catch(err => {
console.error(err);
wx.showToast({
title: '网络连接不稳定,请检查网络连接',
icon: 'none',
duration: 2000
})
})
},
last: function () { //上一页
var num = this.data.numSkip
wx.showLoading({
title: '加载中...',
})
if (num > 5) {
num = num - 5
db.collection('shareData').orderBy('id', 'desc').skip(num).limit(5).get().then(res => {
this.setData({
list: res.data
})
})
} else if (num == 5) {
num = num - 5
db.collection('shareData').orderBy('id', 'desc').limit(5).get().then(res => {
wx.hideLoading()
this.setData({
list: res.data
})
}).catch(err => {
console.error(err);
wx.showToast({
title: '网络连接不稳定,请检查网络连接',
icon: 'none',
duration: 2000
})
})
} else {
wx.hideLoading()
wx.showToast({
icon: "none",
title: '已经是首页了哦',
})
}
this.setData({
numSkip: num
})
},
onLoad: function(options) { //监听页面加载
// var that=this;
wx.showLoading({
title: '加载中...',
})
db.collection('shareData').orderBy('id', 'desc').limit(5).get().then(res => {
// console.log(res.data)
this.setData({
list: res.data
})
wx.hideLoading();
}).catch(err => {
console.error(err);
wx.hideLoading();
wx.showToast({
title: '网络连接不稳定,请检查网络连接',
icon: 'none',
duration: 2000
})
})
},
授权登录
用户想要发表动态等共享内容时候,需要授权登录,同意将自己的openid、昵称等个人信息上传到数据库。这里小程序官方规定是要通过按钮完成的。
首先在onLoad或created中监听是否获得到用户信息(昵称、头像url、openid),将用户信息赋值给标识符,在wxml条件渲染这些标识符是否为空,为空显示授权页面,非空显示发表动态页面。评论时候同理。
上传图片文字,发表动态
这里用到了云存储和云数据库。理论是将文件上传云存储,将所有图片的fileIds数组作为一个字段写进新记录内。
上传文字很简单,就是提交表单,上传数据库。
第一步是选择图片,调用chooseImage方法,从本地图片中选择图片或使用相机方法,可以设置最大上传数量,质量是否压缩,来源图片或相机。选择图片成功后res.tempFilePaths即为临时路径,将其赋值给一个标识符images,需要注意的是为了防止上传多张图片临时路径覆盖,赋值要采用拼接形式。如images:this.data.images.concat(tempFilePaths)
第二步是显示图片,每上传一张要显示在左侧一个小的预览图,这需要列表渲染images数组。
第三步上传图片,js中按照images的长度for循环,每次拿出一张进行上传到云函数。这里要用到正则表达式获取到各图片临时路径的扩展名。上传到云存储函数是wx.cloud.upLoadFile,其中cloudPath赋值上传到云端的路径(可以路径+时间戳+扩展名),filePath赋值临时路径名。上传成功后res.fileID就是上传后的文件唯一id,将其赋值给一个新标识符fileIds,赋值依然采用拼接。
第四步上传数据库,将fileIds、content、openid等信息一起组装成一个新纪录上传到云数据库。
代码展示:
<view wx:else>
<view class="hr"></view>
<!-- <van-cell-group> -->
<van-field value="{{ content }}" border="{{ false }}" maxlength="300" type="textarea" placeholder="对时间表进行适当的介绍吧~" size="large" autosize bind:change="onContentChange" />
<view class="space1"></view>
<image class='img' data-id='{{images}}' bindtap="previewImg" src="{{item}}" wx:for="{{images}}" wx:key="{{index}}"></image>
<image class="img" bindtap="uploadImg" src='/pages/logs/bar/camera.jpg' />
<!-- <van-button size="normal" type="primary" bindtap="submit">允许</van-button> -->
<van-button size="large" type="warning" bindtap="submit">提交</van-button>
</view>
submit: function() {
wx.showLoading({
title: '发表中',
})
// 发布图片到云存储
let promiseArr = [];
for (let i = 0; i < this.data.images.length; i++) {
promiseArr.push(new Promise((reslove, reject) => {
let item = this.data.images[i];
let suffix = /\.\w+$/.exec(item)[0]; // 正则表达式,返回文件扩展名
wx.cloud.uploadFile({
cloudPath: 'shareFiles/' + new Date().getTime() + suffix, // 发布至云端的路径
filePath: item, // 小程序临时文件路径
success: res => {
// 返回文件 ID
console.log(res.fileID)
this.setData({
fileIds: this.data.fileIds.concat(res.fileID)
});
reslove();
},
fail: console.error
})
}));
}
Promise.all(promiseArr).then(res => {
// 插入数据
// if (this.data.fileIds == '') {
// wx.showToast({
// icon: 'none',
// title: '请添加图片',
// })
// } else {
db.collection('shareData').add({
data: {
content: this.data.content,
id: new Date().getTime(),
fileIds: this.data.fileIds,
nickName: this.data.nickName,
avatarUrl: this.data.avatarUrl,
nowTime: new Date().toLocaleString(),
zan: this.data.zan,
isZan: []
}
}).then(res => {
wx.hideLoading();
wx.showToast({
title: '发表成功',
})
wx.navigateBack({
url: '../share/share'
})
}).catch(err => {
wx.hideLoading();
wx.showToast({
title: '发表失败,检查网络连接',
})
})
// }
});
},
onContentChange: function(event) {
this.setData({
content: event.detail
});
},
uploadImg: function() {
// 选择图片
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: res => {
// tempFilePath可以作为img标签的src属性显示图片
const tempFilePaths = res.tempFilePaths
console.log("文件路径temFilePaths:" + tempFilePaths);
this.setData({
images: this.data.images.concat(tempFilePaths)
});
}
})
},
通过fileIds获取图片:
<image data-id='{{item.fileIds}}' bindtap="previewImg" class="imageTabel" src="{{item.fileIds}}" mode="widthFix"></image>
下载图片和预览图片上面有讲过,此处不再多说。
点赞
说来惭愧,当时开发时候没有找到相关办法,是自己摸索出来的。写小程序时候没有深学数据结构与算法,现在再看这部分代码是可以优化的,比如提前结束for循环、0赞直接判断为点赞而不是取消点赞等等。
首先,通过云函数可以在未授权的情况下直接获取openid。
wx.cloud.callFunction({
name: 'getOpenid'
}).then(res => {})
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
//获取用户的openid
exports.main = (event, context) => {
const wxContext=cloud.getWXContext()
return {
event,
openid:wxContext.OPENID,
}
}
用户每次上传动态会在该数据库记录中添加一个空数组isZan[]和变量zan,用来存储点赞人和点赞数。
每次用户点击点赞图标都先获取数据库的isZan,然后遍历有没有该用户的openid,若没有则将用户的openid加进去,zan加一;如果有该用户的openid,则zan减一,删除该用户openid。
需要注意的是,修改别人上传的记录,需要通过云函数,附上代码。
onLike: function(e) {
// 获取openID
var that = this
wx.cloud.callFunction({
name: 'getOpenid'
}).then(res => {
// console.log(res.result.openId);
var openid = res.result.openId;
const _ = db.command;
db.collection('shareData').doc(e.currentTarget.dataset.id).get().then(res => {
// console.log(res)
var haveZan = ''
for (var i = 0; i < res.data.isZan.length; i++) {
// 如果openid存在于iszan中
if (res.data.isZan[i] == openid) {
haveZan = openid
console.log("重复点赞")
wx.showToast({
icon: 'none',
title: '这条已经点过赞了哦',
})
}
}
// console.log("haveZan" + haveZan)
if (haveZan == '') {
wx.cloud.callFunction({
name: 'like',
data: {
shareId: e.currentTarget.dataset.id
},
}).then(res => {
// 局部刷新要判断页码
wx.showLoading({
title: '加载中...',
})
db.collection('shareData').orderBy('id', 'desc').skip(this.data.numSkip).limit(5).get().then(res => {
// console.log(res.data)
this.setData({
list: res.data
})
wx.hideLoading();
}).catch(err => {
console.error(err);
wx.hideLoading();
wx.showToast({
title: '网络连接不稳定,请检查网络连接',
icon: 'none',
duration: 2000
})
})
console.log("点赞成功")
})
}
})
})
},
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
let db = cloud.database();
let _ = db.command;
// 云函数入口函数
exports.main = async(event, context) => {
const wxContext = cloud.getWXContext()
return await db.collection("shareData").doc(event.shareId).update({
data:{
zan: _.inc(1),
isZan: _.push(wxContext.OPENID),
}
})
}
评论
评论和点赞一个道理。都是通过新建一个字段,将用户的文本框输入内容、头像url、昵称、openid插入这个字段,然后列表渲染,特殊的是条件渲染。动态列表如果评论数小于等于两个则直接在动态列表,如果大于两个则在动态下方加一条文本“点击查看n条评论”,同时加上跳转到详情页的navigator.
以下为获取评论的代码:
<view>{{item.colComment[0]}}</view>
<view>{{item.colComment[1]}}</view>
<view wx:if="{{item.colComment.length>0}}" class="goToComment" bindtap='goToComment' data-id="{{item._id}}">查看全部{{item.colComment.length}}评论</view>
<view wx:else class="goToComment" bindtap='goToComment' data-id="{{item._id}}">暂无评论,发句友善的评论吧~</view>
注意最外层是列表渲染动态,所以是item,进入评论页面需要传值到目标页面的生命周期函数中,因为要确保页面是动态的,从而点每个动态进入的都是该动态,而不是其他动态。
收藏
收藏较为简单,我特定创建了一个新集合collection(分享列表的集合为shareData),来存储各用户的收藏信息。
第一步,用户每次收藏,则将该动态的id存进collection集合,赋值给colId。
第二步,在用户收藏列表中获取collection集合的colId,然后根据colId查询shareData集合中的id,传给list;
第三步,通过list在wxml列表渲染。
onCollect: function(e) { //收藏按钮,传值为item
// console.log(e.currentTarget.dataset.id)
var addData = e.currentTarget.dataset.id;
db.collection('collection').where({
colId: addData._id
}).get().then(res => {
// console.log(res)
if (res.data.length != 0)
wx.showModal({
title: '这张时间表已经收藏过啦!',
content: '是否前往我的收藏列表',
success(res) {
if (res.confirm) {
wx.navigateTo({
url: '../myCollection/myCollection'
})
console.log('用户点击确定')
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
else {
db.collection('collection').add({
data: {
colId: addData._id
},
success: res => {
// console.log(res);
wx.showModal({
title: '收藏成功',
content: '是否前往我的收藏列表',
success(res) {
if (res.confirm) {
wx.navigateTo({
url: '/pages/myCollection/myCollection'
})
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
},
fail: err => {
console.log(err);
}
})
}
})
},
//下面是收藏列表中
getList(){
wx.showLoading({
title: '加载中...',
})
var that = this
db.collection('collection').get({
success(res) {
wx.hideLoading()
for (var i = 0; i < res.data.length; i++) {
db.collection('shareData').where({
_id: res.data[i].colId
}).get().then(res => {
// console.log(res.data)
that.setData({
list: that.data.list.concat(res.data)
});
})
}
},
fail(res) {
wx.hideLoading()
console.log("fail", res)
},
})
},
onLoad: function() {
this.getList();
},
待优化内容
1.授权登录界面和形式过时,等待更新。应该设置成进入小程序即弹出授权窗口,不授权登录则无法体验小程序,而不是仅仅上传和评论收藏时候,这样会方便很多。
2.已点赞已收藏没有颜色的变化(颜色变化可以实现,但刷新后用户),