首先对于这个文档,我感到很抱歉,本来早就该写好的东西,拖到现在。 不得不说意外和生活总是一起到来,不多说现在就开始

一、项目介绍

这个项目实际上是我的一个同学介绍给我的,老板是一个篮球培训机构老总,他希望可以做一个小程序,帮助上课的孩子们签到,同时学生,家长,管理员都能使用这个小程序。
学生端的功能:可以获取到每日管理员发表的公告,包括图片,文字(这是一个我认为的难点),学生还需要使用签到功能,然后每次签到都会在数据库中写入,之后还可以查看自己的签到情况,检查自己的情况是否有错,可以反馈给老师,让老师消除错误。
家长端的功能:家长可以登录,并且绑定多个孩子,可以获取某个孩子的签到情况。可以获取到每日管理员发表的公告,包括图片,文字。
管理员端功能:可以自动生成学生的学号,即有同学新加入时,需要通过管理员提供的一系列连续的学号,学生以此作为登录或注册的唯一凭证。如果学生忘记该凭证,也可以通过管理员查询该学生名字,获取到其学号。超级管理员可以生成一系列管理员号,拥有对一般管理员增删的权限,该账号只有一个,所以不赘述。可以发表每日的推文。
目前能想到的功能应该就是这些,详情往下看。

二、项目的结构

miniprogram-4 // 项目根目录 ├─cloudfunctions // 云函数功能,这里是小程序开发提供的 │ ├─callback │ ├─echo │ ├─login │ └─openapi └─miniprogram // 项目主体 ├─cloud // 云函数自定义及实现 │ ├─addStudent │ ├─delSign │ ├─delStudent │ ├─getAllStudent │ ├─getopenid │ ├─getSign │ ├─getStudent │ ├─getStudentMach │ ├─getTweets │ ├─modifySign │ ├─Sign │ └─upStudent ├─images // 图片目录 ├─page // 页面根目录 │ ├─AdminsterPage // 管理员页面目录 │ │ ├─addAdminster // 添加管理员 │ │ ├─addStudent // 添加学生 │ │ ├─EditPassage // 编辑页面 │ │ ├─Search // 搜索页 │ │ ├─SearchByName // 以名字搜索页 │ │ └─Tabbar // Tabbar主页 │ ├─CheckData // 检索数据页 │ ├─ParentPage // 父母页面目录 │ │ ├─AlterData // 修改数据 │ │ ├─CheckData // 检索数据,这里是检索自己孩子的 │ │ └─Tabbar // Tabbar主页 │ ├─RegisterPage // 注册页面,包含三个模块,利用wx:if判断 │ ├─ShowPassage // 展示管理员发送的推文详情 │ ├─showStudent // 展示学生签到的详情 │ ├─SignMainPage // 登录页面 │ ├─StudentPage // 学生页面目录 │ │ ├─Reflect // 反馈页面,即可以向软件开发者提意见 │ │ └─Tabbar // Tabbar主页 │ └─ToChooseSignUp // 选择签到类型, └─style // 基本的样式,无需我们修改

二、页面详情

2.1 小程序的配置情况

  1. {
  2. "pages": [
  3. "page/SignMainPage/SignMainPage", // 谁放在第一行就是第一个打开的页面
  4. "page/showStudent/showStudent",
  5. "page/RegisterPage/RegisterPage",
  6. "page/CheckData/CheckData",
  7. "page/StudentPage/Tabbar/Tabbar",
  8. "page/AdminsterPage/addAdminster/addAdminster",
  9. "page/StudentPage/Reflect/Reflect",
  10. "page/AdminsterPage/addStudent/addStudent",
  11. "page/ShowPassage/ShowPassage",
  12. "page/ParentPage/Tabbar/Tabbar",
  13. "page/ParentPage/AlterData/AlterData",
  14. "page/ParentPage/CheckData/CheckData",
  15. "page/ToChooseSignUp/ToChooseSignUp",
  16. "page/AdminsterPage/EditPassage/EditPassage",
  17. "page/AdminsterPage/Search/Search",
  18. "page/AdminsterPage/Tabbar/Tabbar",
  19. "page/AdminsterPage/SearchByName/SearchByName"
  20. ],
  21. "window": {
  22. "backgroundColor": "#FFFFFF", // 背景颜色
  23. "backgroundTextStyle": "light", // 深色模式?
  24. "navigationBarBackgroundColor": "#1E90FF", // 签到小程序文本的背景
  25. "navigationBarTitleText": "签到小程序", // 标题
  26. "navigationBarTextStyle": "white", // 标题颜色
  27. "enablePullDownRefresh": true // 允许下拉刷新
  28. },
  29. "sitemapLocation": "sitemap.json",
  30. "style": "v2"
  31. }

2.2 登录页面

image.png
首先是这个布局,实际上并不好看。

  1. <view class="signTab">
  2. <button data-id="0" bindtap="handleSign" style="font-size: 45rpx;">我是学生</button>
  3. <button data-id="1" bindtap="handleSign" style="font-size: 45rpx;">我是家长</button>
  4. <button data-id="2" bindtap="handleSign" style="font-size: 45rpx;">管理员</button>
  5. </view>
  6. <button style="margin-top:70%" bindtap="navigateToSignIssue">登录遇到问题</button>
  1. 需要注意的是,wxml中均是一个个view堆叠,我的理解是与html中的div相应<br />`data-id=`是一个属性值,用于`SignMainPage.js`读取数据。
  1. handleSign(e) {
  2. wx.showLoading({
  3. title: '',
  4. mask: true
  5. })
  6. // wx.showLoading将调用loading动画,title为空即没有提示信息,mask为true即是添加蒙层
  7. console.log("在这里调用showLodging")
  8. // 所有的consol.log("")均是输出提示信息,对功能没有实际影响
  9. let _id = e.currentTarget.dataset.id
  10. // 获取上文提到的data-id,这表示的是用户类型
  11. wx.setStorageSync('userNumber', _id)
  12. // 设置缓存,命名为userNumber
  13. wx.cloud.callFunction({
  14. name: 'getopenid'
  15. // 调用云函数中定义好的getopenid
  16. // 关于then与catch的用法,
  17. // 见https://www.cnblogs.com/skuld-yi/p/14599594.html
  18. }).then(res => {
  19. wx.hideLoading()
  20. console.log('在这里调用hideLodging')
  21. wx.setStorageSync('openid', res.result.openid)
  22. // 设置缓存openid
  23. console.log('handleSign openid', wx.getStorageSync('openid'))
  24. this.login() // 调用登录函数
  25. })
  26. .catch(res => {
  27. wx.hideLoading()
  28. console.log('在这里调用hideLodging')
  29. console.log(res)
  30. // 隐藏loading动画
  31. })
  32. },
  33. // 需要注意的是,这里的handleSign(e)的定义写在与onLoad同级下。
  1. 这里需要接着找我们的`getopenid`云函数
  1. // 云函数入口文件
  2. const cloud = require('wx-server-sdk')
  3. cloud.init()
  4. // 云函数入口函数
  5. exports.main = async (event, context) => {
  6. const wxContext = cloud.getWXContext()
  7. return {
  8. event,
  9. openid: wxContext.OPENID,
  10. appid: wxContext.APPID,
  11. unionid: wxContext.UNIONID,
  12. }
  13. }

image.png
关于云函数更多见:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions.html
还有找到openid之后我们还需要调用this.login()

  1. login() {
  2. wx.hideHomeButton({
  3. success: (res) => {
  4. console.log('成功') // 隐藏左上角的home键
  5. },
  6. })
  7. wx.showLoading({
  8. title: '', // 展示loading动画
  9. mask:true
  10. })
  11. console.log("在这里调用showLodging")
  12. let openid = wx.getStorageSync('openid') // handleSign中已经获得了openid
  13. let userNumber = wx.getStorageSync('userNumber') // 获取用户类型
  14. if (userNumber == '0' || userNumber == '1' && openid != '') {
  15. console.log('openid1:', openid)
  16. //这里不要用用户上传的时候自动添加的_openid字段
  17. db.collection(this.data.userType[userNumber].value) // 获取用户类型的所有值(见下图)
  18. .where({
  19. openid: openid // 条件语句等于sql中的where
  20. }).get()
  21. .then(res => { // 成功后执行
  22. console.log('res:', res)
  23. if (res.data.length == 1) { // 如果找到一个
  24. console.log('这台设备使用过这个小程序,且在数据库找到这台设备', res)
  25. if (userNumber == 0) {
  26. wx.setStorageSync('studentID', res.data[0].studentID)
  27. wx.setStorageSync('studentName', res.data[0].studentName)
  28. wx.hideLoading()
  29. console.log('在这里调用hideLodging')
  30. wx.setStorageSync('status', true)
  31. wx.reLaunch({
  32. url: '../StudentPage/Tabbar/Tabbar', // 找到学生主页
  33. })
  34. } else if (userNumber == 1) {
  35. wx.setStorageSync('parentsName', res.data[0].name)// 设置父母的名字
  36. wx.setStorageSync('childData', res.data[0].child) // 设置孩子数据
  37. wx.setStorageSync('_id', res.data[0]._id)
  38. wx.setStorageSync('status', true)
  39. console.log('在这里调用hideLodging')
  40. wx.hideLoading()
  41. wx.reLaunch({
  42. url: '../ParentPage/Tabbar/Tabbar', // 跳转父母页面
  43. })
  44. } else {
  45. console.log('在这里调用hideLodging')
  46. wx.hideLoading()
  47. console.log('userNumber出错')
  48. }
  49. } else {
  50. console.log('在这里调用hideLodging')
  51. wx.hideLoading()
  52. console.log('这台设备使用过这个小程序,但是数据库没有找到这台设备,转去注册')
  53. wx.setStorageSync('status', false)
  54. wx.redirectTo({
  55. url: '../RegisterPage/RegisterPage',
  56. })
  57. }
  58. })
  59. .catch(res => {
  60. console.log('在这里调用hideLodging')
  61. wx.hideLoading()
  62. console.log('查询失败,云函数出错', res)
  63. })
  64. }
  65. else if (userNumber == '2') {
  66. let adminID = wx.getStorageSync('adminID')
  67. let adminPw = wx.getStorageSync('adminPw')
  68. const _ = db.command
  69. db.collection('admin').where(_.and([{
  70. adminID: adminID
  71. //见这里
  72. //https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/command/Command.and.html
  73. }, {
  74. adminPw: adminPw
  75. }])).get()
  76. .then(res => {
  77. console.log(res)
  78. if (res.data.length > 0) {
  79. wx.setStorageSync('adminID', res.data[0].adminID)
  80. wx.setStorageSync('adminPw', res.data[0].adminPw)
  81. wx.setStorageSync('adminName', res.data[0].adminName)
  82. wx.setStorageSync('status', true)
  83. console.log('在这里调用hideLodging')
  84. wx.hideLoading()
  85. wx.reLaunch({
  86. url: '../AdminsterPage/Tabbar/Tabbar',
  87. })
  88. } else {
  89. console.log('在这里调用hideLodging')
  90. wx.hideLoading()
  91. wx.setStorageSync('status', false)
  92. wx.reLaunch({
  93. url: '../RegisterPage/RegisterPage',
  94. })
  95. }
  96. })
  97. .catch(res => {
  98. console.log('在这里调用hideLodging')
  99. wx.hideLoading()
  100. console.log(res)
  101. })
  102. } else {
  103. console.log('在这里调用hideLodging')
  104. wx.hideLoading()
  105. console.log('这台设备没有使用过这个小程序,不转跳让用户选择身份')
  106. }
  107. },

image.png

2.3 推文详情页功能

image.png
首先是前端页面:

  1. <view class="passageUser">
  2. <view class="passageAvatar">
  3. <image class="Avatar" src="../../images/user.png"></image>
  4. </view>
  5. <view class="passageUserName">
  6. <view style="line-height:100rpx;font-size:40rpx">
  7. <text>{{tweets.adminName}}</text>
  8. </view>
  9. </view>
  10. </view>
  11. <view class="title" style="font-size:40rpx"><text>{{tweets.title}}</text></view> <!-- 此处为标题 -->
  12. <view class="passage" style="font-size:40rpx"><text>{{tweets.text}}</text></view>
  13. <view class="container">
  14. <view class="divLine"></view>
  15. </view>
  16. <view wx:if="{{userNumber=='2'}}">
  17. <button bindtap="deleteTweets" type="warn" style="font-size:40rpx;width:30%;margin-top:20% ;position: relative;background-color:red;color: white">
  18. 删除
  19. </button>
  20. </view>
  21. <view class='container_bottom_imglist'>
  22. <block wx:for='{{tweets.photoList}}' wx:key=''>
  23. <view class='container_bottom_img'>
  24. <image class='img' bindtap='previewImg' data-img='{{item}}' src='{{item}}' mode="aspectFill"></image>
  25. </view>
  26. </block>
  27. </view>
  1. 这里所有的`tweets``tweets.photoList``tweets.adminName``tweets.text`等是在JavaScript文件中有所定义的。
  1. data: {
  2. tweets_id: '',
  3. tweets: '',
  4. userNumber: ''
  5. },
  1. 这里的`tweets`不是一个简单的数据,而是一个对象
  1. onLoad: function (options) { // 在onload的时候就调用的函数
  2. console.log('options', options) // options,在输出的时候是一个空的字典
  3. let that = this
  4. this.setData({
  5. tweets_id: options._id, // 这里是前面一个Tabbar页面所推过来的option,
  6. userNumber: wx.getStorageSync('userNumber')
  7. })
  8. console.log('userNumber', this.data.userNumber)
  9. wx.cloud.database().collection('tweets').where({ // 获取集合中符合条件的记录
  10. _id: that.data.tweets_id // 保存当时的现场
  11. }).get()
  12. .then(res => {
  13. console.log(res)
  14. that.setData({
  15. tweets: res.data[0] // 这里的结果返回出来一定是一个数组
  16. }) // 所以这里要是用下标为0的操作,才能读取
  17. })
  18. .catch(res => {
  19. console.log('获取推文失败', res)
  20. })
  21. },
  1. navToShowPassage:function(e){
  2. console.log('e',e)
  3. let _id = ''
  4. _id = e.currentTarget.dataset.tweets._id // currentTarget:当前组件对象
  5. if(_id!==''){
  6. wx.setStorageSync('tweets_id', _id)
  7. wx.navigateTo({
  8. url: '../../ShowPassage/ShowPassage?_id='+_id // ._id 是数据库中的数据,
  9. // 具体是哪里设置的这个值
  10. // 是在tabbar页中onload函数中获取
  11. // 这里不展开讲
  12. })
  13. }else{
  14. console.log('推文的_id出错')
  15. }
  16. },

image.png
至此我们获取到了推文的详情,并显示在页面上。
还有一个重点,我们如何将文件上传并读取?

2.4 编辑推文上传文件功能

前端页面:
image.png
丑的很依旧。

  1. <view class="passageUser">
  2. <view class="passageAvatar">
  3. <open-data type="userAvatarUrl">
  4. </open-data>
  5. </view>
  6. <view class="passageUserName" style="font-size:40rpx;font-weight:800">
  7. <open-data type="userNickName">
  8. </open-data>
  9. </view>
  10. </view>
  11. <view class="passage" style="font-size:40rpx;">
  12. <input class="title" type="text" bindinput="getTitle" value="标题:"></input>
  13. <textarea bindinput="getTextarea" maxlength='-1' value="键入文章"></textarea>
  14. <!-- 加入的代码 -->
  15. <view class='container_bottom_imglist'>
  16. <block wx:for='{{photo}}' wx:key=''>
  17. <view class='container_bottom_img'>
  18. <image class='clear_img' bindtap='clearimg' data-index='{{index}}' src="/images/clear.png"></image>
  19. <image class='img' bindtap='previewImg' data-img='{{item}}' src='{{item}}'></image>
  20. </view>
  21. </block>
  22. <image style="" bindtap='headUpImage' class='add_img' src='/images/addimg.png'></image>
  23. </view>
  24. <!-- 加入的代码 -->
  25. </view>
  26. <view class="submit">
  27. <button type="primary" bindtap="headUpTweets">
  28. <view style="font-size:40rpx;"><text>提交</text></view>
  29. </button>
  30. </view>
  31. <!-- </view> -->
  1. 重点看到22行里,有一个`bindtap='headUpImage'`,显然我们需要将这个`headUpImage`看明白。
  1. data: {
  2. title:'',
  3. photo: [], //图片
  4. },
  5. headUpImage: function (e) {
  6. let that = this // 保存当前的this
  7. wx.chooseImage({ // 微信自带的选择图片
  8. count: 9, // 参数:可选择多少张,最高值好像就是9张
  9. sizeType: ['compressed'], // 参数:所选图片的尺寸,这里用的压缩图
  10. sourceType: ['album', 'camera'], // 参数:可以从哪里选择图片,相册与相机
  11. success: function (res) { // 参数:是否成功,调用一个函数。利用其返回值
  12. var newphoto = that.data.photo; // 这里是将data中的空列表拿来,方便调用
  13. that.setData({
  14. photo: newphoto.concat(res.tempFilePaths),
  15. }) // 先说一下push和concat,这两个方法很像,
  16. // 都是将元素正序放入集合中,区别在于,push是在原数组上添加元素
  17. // 而concat是存入新数组中。
  18. // tempFilePaths本地临时文件路径列表 (本地路径)
  19. console.log('photo:',that.data.photo)
  20. },
  21. fail: function () {} // 失败函数未定义
  22. })
  23. },

image.png
直到这里还是选择图片,还有将图片放入内存中。
接下来是上传推文

  1. headUpTweets: async function (e) {
  2. wx.showLoading({
  3. title: '上传中...',
  4. mask:true
  5. })
  6. let that = this
  7. let title = that.data.title // 从前端获取的title,通过一个getTitle函数
  8. let time = util.formatTime(new Date) // 获取时间
  9. let photoList = await that.savePhoto() // await等待执行完成
  10. console.log('photoList:',photoList)
  11. if (this.data.textarea != '') {
  12. db.collection('tweets').add({ // 在数据库中写入数据
  13. data: {
  14. title:title,
  15. text: that.data.textarea,
  16. photoList:photoList,
  17. time: time,
  18. adminID:wx.getStorageSync('adminID'),
  19. adminName:wx.getStorageSync('adminName')
  20. }
  21. })
  22. .then(res => {
  23. console.log('res',res)
  24. wx.showModal({
  25. title: '恭喜',
  26. content: '发生推文成功'
  27. })
  28. console.log('上传成功')
  29. setTimeout(function() {
  30. wx.navigateBack({})
  31. }, 1500)
  32. })
  33. .catch('上传失败')
  34. } else {
  35. wx.hideLoading()
  36. wx.showModal({
  37. title:'推文不能为空'
  38. })
  39. console.log('推文不能为空')
  40. }
  41. wx.hideLoading()
  42. },
  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2759039/1655347440952-49bee7ae-5a1d-448f-bcf8-719b0695a98c.png#clientId=u4955171f-8430-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=735&id=u4b2e67ba&margin=%5Bobject%20Object%5D&name=image.png&originHeight=735&originWidth=415&originalType=binary&ratio=1&rotation=0&showTitle=true&size=26254&status=done&style=none&taskId=ubca12cfa-e353-49be-8b97-a94a787d038&title=%E5%9B%BE6%20%E8%BF%99%E9%87%8C%E7%9A%84%E5%AF%B9%E5%BA%94wx.showModal&width=415 "图6 这里的对应wx.showModal")<br />接下来是`savePhoto()`
  1. savePhoto: async function() {
  2. var that = this;
  3. var photo = that.data.photo; // 临时图片地址
  4. var photoTemp = [];
  5. for (var i = 0; i < photo.length; i++) {
  6. //等待存储图片完成
  7. await new Promise((resolve, reject) => {
  8. wx.cloud.uploadFile({
  9. cloudPath: 'mernorandum/' + that.guid() + '.png', // 上传至云端的路径
  10. filePath: photo[i], // 小程序临时文件路径
  11. success: res => {
  12. // 返回文件 ID
  13. photoTemp[i] = res.fileID;
  14. resolve()
  15. },
  16. fail(err) {
  17. console.log(err)
  18. reject()
  19. }
  20. })
  21. })
  22. }
  23. console.log('photoTemp:',photoTemp)
  24. return photoTemp;
  25. },

Promise,简单来说就是一个容器,里面保存着某个未来才会结束的时间(通常是一个异步操作的结果) 从语法上来说,Promise是一个对象,从它可以获取异步操作的消息。 见: https://www.jianshu.com/p/fe0159f8beb4 https://www.ruanyifeng.com/blog/2015/05/async.html https://zhuanlan.zhihu.com/p/348557507