小程序开发技术总结

源码地址: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
2020-2-16 160728.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

生命周期函数

页面生命周期

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

页签切换我使用的是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

调用api

小程序中调用了天气api网站的api,以下为获取的天气信息:

CC5%I4GV2W8J349M64{UPHJ.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.已点赞已收藏没有颜色的变化(颜色变化可以实现,但刷新后用户),

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小海绵【vincewm】

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值