从入门到提升—失物招领小程序(云开发+即时通信)

写在前面

本文主要聊聊该项目的云开发技术点和js逻辑,IM即时通信以后会写。经过本文你将学习微信小程序云函数的使用、组件间通讯的各类方法、根据功能需求设计js逻辑、promise相关用法、页面分页加载等。
github项目地址javascript

完整效果展现

项目分析

  • 本校内已有三个失物招领QQ群,每一个群1900+人(包括重复加群),上课期间,天天每一个群的信息数99+(包括重复发、图片文字分开发等)。另外,据管理员称,群内虽然少有闲话,但失物的找回率不高,不少同窗不知道有信息公告或者由于信息太多而错过了。
  • 微信上可搜索到的失物招领小程序有很多,考虑到用户的心理,好心人和失主之间的联系功能,不少平台都是直接展现联系方式,即便有经过学号登陆才可查看信息,那也是对用户信息的不负责。
  • 基于上述分析,这款小程序的需求:有分类,能搜索,快速发布,即时联系,不用加一堆的群,也不担忧错过了信息。明确了需求,咱们才能更好的设计代码逻辑,下面开始!

发布功能(本地代码)

思路分析

  • 功能:发表前提醒受权;表单为空不可提交;能多选图片并上传相关内容。
  • 困难点:使用promise.all实现先上传图片,拿到图片的云路径后,再将用户输入的发布信息所有上传。
  • 实现:
    • 点击"当即发布"按钮后,调用组件inputDetail中的方法checkUploadForm(带参数releaseType,方便后面上传数据是添加到失物招领的found集合仍是寻物启事的lost集合),表单内容不为空开始上传;
    • 对上传图片的缓存路径执行forEach,将每张图片的缓存路径传递给promise函数promiseUplodImg(函数resolve上传完成后每张图片的云路径)上传图片;
    • 同时将每张图片执行的该函数push进一个数组中,再执行promise.all(该方法接收一个每一项都是promise函数的数组);
    • 最后,经过.then(接收一个数组,包含全部图片的云路径)将用户输入的信息上传。

代码实现

// miniprogram/pages/release/release.js
data: {
    clickStatus: 0,
    releaseTypes: ['found', 'lost'],
  }
//当即发布按钮上绑定该方法
checkUploadForm: function () {
    let that = this
    this.setData({
      isUpload: true
    })    // 声明上传状态
    this.inputDetail = this.selectComponent('#inputDetail')
    this.inputDetail.checkUploadForm(that.data.releaseTypes[that.data.clickStatus])
//调用组件inputDetail上的checkUploadForm方法,传参found || lost
}
复制代码
// // components/inputDetail/inputDetail.js
checkUploadForm(releaseType) {
  //releaseType 根据参数肯定发布的类型是失物招领仍是寻物启事
    let that = this
    let promises = []
    if (that.data.releaseText && that.data.thingsType) {
        that.setData({
            releaseType,
        })
        if (that.data.imgLocalPath.length > 0) {
          wx.showLoading({
            title: '正在发布...'
          });

          that.data.imgLocalPath.forEach(element => {
            promises.push(that.promiseUplodImg(element))
          })
          // 遍历缓存图片路径,将多个promise函数放入数组promises中
          // 拿到全部图片的云路径
          Promise.all(promises)
            .then((imgCloudPath) => {
              console.log(imgCloudPath);
              that.uploadRelease(imgCloudPath)
            })
            .catch((err) => { console.log(err); })

        } else {
          //用户没有选择图片,则直接上传发布信息
          that.uploadRelease()
        }
      } else {
        wx.showToast({
          title: '内容或分类为空',
          image: '../../asserts/icons/my_icon_still@2x.png',
          duration: 1800
        })
      }
    },
    promiseUplodImg(element) {
      return new Promise((resolve, reject) => {
        //一次只能上传一张图片到数据库
        const filePath = element;
        let a = filePath.lastIndexOf('.');
        let b = filePath.lastIndexOf('.', a - 1);
        let c = filePath.substring(b + 1, a);
        const cloudPath = c + filePath.match(/\.[^.]+?$/);
        wx.cloud.uploadFile({
          filePath,
          cloudPath,
          success: (res) => {
            console.log('上传成功', res)
            resolve(res.fileID)
            //每张图片的云路径
          },
          fail: (err) => {
            console.log('上传失败', err)
          }
        });
      })
    },
    uploadRelease(imgCloudPath) {
      let that = this
      wx.cloud.callFunction({
        name: "uploadInput",
        data: {
          releaseType: that.data.releaseType,
          releaseImg: imgCloudPath,
          releaseText: that.data.releaseText,
          thingsType: that.data.thingsType,
          releaseCall: that.data.releaseCall,
          releaseRemind: that.data.releaseRemind,
          //用户输入的所有信息
        },
        success: () => {
          wx.hideLoading();
          const releaseType = that.data.releaseType
          wx.switchTab({
            url: `/pages/${releaseType}/${releaseType}`
          })
          // 跳转到发布类型对应的页面
          wx.hideNavigationBarLoading()
        },
        fail: (err) => {
          console.log(err);
        },
        complete: () => {
          that.setData({
            releaseText: '',
            imgLocalPath: [],
            thingsType: '',
            releaseCall: '',
            releaseRemind: false,
            releaseType: 'found',
            typeIndex: 0,
          })
        }
      })
    },
复制代码

发布功能(云函数)

思路分析

  • 功能:调用云函数,将发布内容上传。
  • 实现: 使用promise先将发布的信息上传至对应的集合,再将发布记录的id和发布者的openId上传至集合user-release。这样存储便于后面读取数据渲染界面,每一条发布记录的id经过user-release集合与发布者信息绑定在一块儿。(id是云开发数据库中给每条记录自动生成的_id)

数据库记录示例

代码实现

// 云函数uploadInput
const getTime = function () {
    return new Promise((resolve, reject) => {
      var now = new Date();
      var month = now.getMonth() + 1;    
      var day = now.getDate();           
      var hour = now.getHours() + 8;      //没加8被坑
      var minute = now.getMinutes();          
      var time = ''
      if (month < 10) time += "0";
      time += month + "-";
      if (day < 10) time += "0";
      time += day + " ";
      if (hour < 10) time += "0";
      time += hour + ":";
      if (minute < 10) time += '0';
      time += minute;
      resolve(time)      
    })
  }
exports.main = async (event, context) => {
  const userInfo = event.userInfo
  const createTime = await getTime()
  return await db.collection(event.releaseType).add({
    data: {
      releaseType: event.releaseType,
      releaseImg: event.releaseImg,
      releaseText: event.releaseText,
      thingsType: event.thingsType,
      releaseCall: event.releaseCall,
      releaseRemind: event.releaseRemind,
      createBy: userInfo.openId,
      createTime: createTime,
      deleted: false
    }
  })
    .then(res => {
      return db.collection('user-release').add({
        data: {
          releaseId: res._id,
          userId: userInfo.openId,
          deleted: false,
        }
      })
    })
}
复制代码

效果展现

加载发布的信息(本地代码)

思路分析

  • 功能:在“所有”页加载所有信息,点击类别加载不一样类的信息。
  • 实现:
    • 在当前页面调用云函数getRelease,并将获取到的页面数据存入一个中间变量middleArr(这样便于作分页/分类加载);
    • 而后调用函数showPage判断当前点击的类别,从middleArr中筛选出与点击类别匹配的数据,并将数据放入releaseIndo传给组件infoCard来展现用户发布的信息。

代码实现

// components/infoCard/infoCard.js
// 获取到的数据传入组件进行渲染
  properties: {
    releaseInfo: {
      type: Array,
      value: []
  }
<!--components/infoCard/infoCard.wxml -->
<block wx:for="{{releaseInfo}}" wx:key="index" wx:for-item="releaseItem"> </block>
复制代码
<!--miniprogram/pages/found/found.wxml 发布信息展现页-->
<view class="navbar">
  <scroll-view scroll-x="{{true}}">
    <category tabs="{{['所有','证件','书籍文具','电子设备','生活用品','其余']}}" catch:onTitleChange="onTitleChange"></category>
  </scroll-view>
</view>
<view class="foundContent">
  <view class="inforCards">
    <infocard id="infoCard" class="infoCard" releaseInfo="{{releaseInfo}}"></infocard>
  </view>
</view>
复制代码
// miniprogram/pages/found/found.js
data: {
    tabbarText: 'found',
    releaseInfo: [],  // 传递给组件的数据
    typeTitle: ['all', 'card', 'booktool', 'electroic', 'lifetool', 'others'],
    thingsType: 'all', //默认点击的是‘所有’类
    middleArr: [],
  },
  onTitleChange(e) {
    //判断用户点击的类别
    this.setData({
      thingsType: this.data.typeTitle[e.detail]
    })
    this.showPage()
  },
onShow: function () {
	//加载页面,调用云函数
    let that = this
    wx.showNavigationBarLoading();
    wx.cloud.callFunction({
      name: 'getRelease',
      data: {
        tabbarText: that.data.tabbarText,
        //根据点击的tabbar=失物招领或寻物启事,获取不一样集合中的数据
      },
      success: res => {
        that.setData({
          middleArr: res.result,
          //成功返回的数据放入中间变量
        })
        that.showPage()
      },
      fail: (err) => {
        console.log(err)
      },
      complete: () => {
        wx.hideNavigationBarLoading()
      }
    })
  },
   showPage() {
    let that = this
    if (that.data.thingsType == 'all') {
 //若是点击的是‘所有’,则直接将中间变量中的数据传给releaseInfo,组件获取数据进行渲染
      that.setData({
        releaseInfo: that.data.middleArr
      })
    } else {
      let newArr = []
      const val = that.data.thingsType
      //不然遍历中间变量中的数据,将知足条件的数据传给组件
      that.data.middleArr.forEach(item => {
        if( item.thingsType == val){
          newArr.push(item)
        }
      })
      that.setData({
        releaseInfo: newArr
      })
    }
  },
复制代码

加载发布的信息(云函数)

思路分析

  • 调用云函数getRelease,在集合user-release筛选出未删除数据(获得一个数组,数组每一项都是一个对象,每一个对象中包含每一发布信息的releaseId和与之对应的发布者openId);
  • 而后遍历数组的每一个对象的releaseId字段,根据该字段上的内容和未删除deleted:false在集合found || lost筛选出对应失物招领或寻物启事的发布信息;
  • 在遍历过程当中,还要根据每条发布信息上的createBy字段,在集合goodUser筛选出对应的发布者信息,并将每条发布者信息放入发布信息的createBy中;
  • 最后,将每条已经存入发布者信息的发布信息unshift进入数组returnResult中(保证将最新的发布信息渲染在最前面),返回returnResult

数据库记录示例

代码实现

// 云函数getRelease
exports.main = async (event, context) => {
  let releaseList = await db.collection('user-release')
    .where({
      deleted: false
    })
    .get()
  let returnResult = []
  for (let item of newReleaseList) {
    const oneRelease = await db.collection(event.tabbarText)
    // 调用云函数传入 event.tabbarText=found||lost
    // 在found || lost 集合中获取数据
      .where({
        _id: item.releaseId,
        deleted: false
      })
      .get()
    // 通过上面的代码筛选,newReleaseList中可能存在空项
    // 能够直接返回returnResult.push(oneRelease.data)进行查看理解 
    if (oneRelease.data.length > 0) {
      const userInfo = await db.collection('goodUser')
        .where({
          openId: oneRelease.data[0].createBy
        })
        .get()
      oneRelease.data[0].createBy = userInfo.data[0];
      returnResult.unshift(oneRelease.data[0])
    }
  }
  return returnResult
}
复制代码

效果展现

分页加载

思路分析

  • 设置每次获取4条发布信息、默认当前页为1,当用户下拉触底时,执行onShow
  • 调用云函数getRelease再次请求数据,将返回的数据传递给组件渲染;
  • 在云函数getRelease中,将获取的数组使用slice方法,slice接受的参数为:- (当前页 * 每次获取数) ,用参数newRelease接收该方法返回的新数组。新数组中将是最新的(当前页 * 每次获取数)条发布信息。

代码实现

// miniprogram/pages/found/found.js
onReachBottom: function () {
    this.setData({
      currentPage: this.data.currentPage + 1
    })
    console.log(this.data.currentPage);
    this.onShow()
  },
wx.cloud.callFunction({
      name: 'getRelease',
      data: {
        tabbarText: that.data.tabbarText,
        currentPage: that.data.currentPage
      },
  ......
复制代码
// 云函数 getRelease
let releaseList = await db.collection('user-release')
    .where({
      deleted: false
    })
    .get()
  const currentPage = event.currentPage
  const releaseSize = 4
  const sliceRelease = releaseSize  * currentPage
  const newReleaseList = releaseList.data.slice(-sliceRelease)
  // 在这里插入实现分页加载
  let returnResult = []
  for (let item of newReleaseList) {
    const oneRelease = await db.collection(event.tabbarText)
      .where({
........
复制代码

效果展现

技术总结

  • 组件间的通讯除了可使用properties,也能够尝试selectComponent来调用组件上的方法,将数据传过去。
  • 在实际项目中,总会遇到不少须要按步骤执行的函数,稍不注意,获取的数据就是错的,经过使用promise系列方法能够很好的解决异步问题。
  • 在小程序中频繁屡次使用setData是很是消耗性能的,在设计js逻辑、构建函数、设置变量的时候要多加考虑项目功能先后的联系,从而保证代码严谨而且减少资源消耗。
  • 云开发能够很好的帮助咱们同时完成先后端的配置,在开发过程当中加深咱们对后端数据的理解和运用。
相关文章
相关标签/搜索