微信小程序《西电失物招领》

Posted by wsxq2 on 2019-01-27
TAGS:  微信小程序

1 目标场景

用于解决大学校园中经常丢东西却难以找回的问题

设计本小程序的初衷就是因为我室友经常丢东西,我也有因为丢东西而无可奈何的经历。在丢东西后,我首先想到的是仔细回想,反复确认,试图找到。如果失败,则向西电小喇叭寻求帮助,它会在QQ空间中发布Lost信息。但是问题在于,西电小喇叭每天会发布大量的通知信息,很容易就把我的Lost信息给淹没了。并且QQ空间好像并没有提供方便的搜索功能。另外,就算你是幸运儿(因为这真的是个低概率事件),你通过西电小喇叭找回了丢失的东西。但是捡到东西的人和丢东西的人付出的时间和精力通常是巨大的。

下面先给出一个可能的丢东西的人的操作过程:

  1. 在班群中询问是否有人捡到自己丢的东西
  2. 若无,在自己空间发布(并号召好友转发)
  3. 与此同时,向西电小喇叭提供信息,西电小喇叭发布相应的Lost信息(这里为了方便快捷,西电小喇叭通常都采用复制粘贴的方式,这就可能会导致提供的信息不全,或信息冗余,或格式不统一等问题)
  4. 在西电小喇叭的QQ空间(貌似西电小喇叭不只一个号,所以将会加大找到失物的难度)查找相应的Found信息

然后再给出一个可能的捡到东西的人的操作过程(和丢东西的人类似):

  1. 他会选择在班群中询问
  2. 若未找到失主,在自己的空间发布(并号召好友转发)
  3. 与此同时,向西电小喇叭提供信息,西电小喇叭发布相应的Found信息(这里为了方便快捷,西电小喇叭通常都采用复制粘贴的方式,这就可能会导致提供的信息不全,或信息冗余,或格式不统一等问题,从而加大了失主的找到失物的难度)
  4. 然后在西电小喇叭的QQ空间(貌似西电小喇叭不只一个号,所以将会加大找回失物的难度),查找相应的Lost信息(这一步对于捡到东西的人来说应该很少有人会做,因为结果是未知的,效率低下)

2 需求分析

  1. 能记录用户信息(如真实姓名,联系电话等),用于联系失主,并将其与微信账号绑定
  2. 能登记丢失和捡到的物品,别人可以查询到登记的信息
  3. 针对不同常见物品设置相应的关键字段(如一卡通为学号),在字段为空时自动不显示(如一卡通不需要图片和时间地点信息就能百分百匹配成功)。从而避免对于每个物品都要填写固定的字段,改善了用户体验(显示时也会自动不显示未填写的字段)
  4. 可以查看自己已经发布的捡到或丢失信息,并可以进行修改(即重发布)
  5. 处理成功归还后的操作(如在数据库中记录归还对象、归还日期30天,标记为已归还(此处是否就新建一个表用于记录归还的元数据信息?)等等)
  6. 可以通过搜索(可以使用多个单词,如手机 2018-12-28 竹园餐厅)快速找到自己丢失的物品
  7. 可以自动匹配丢失的物品和捡到的物品,并跳转到匹配结果页面
  8. 自动通知。在有捡到的用户登记后,如果确定已有的丢失与之匹配,则及时通知丢东西的用户;反之,若在丢失的用户登记后,如果之前有人登记了相关的捡到信息,则会通知捡到东西的用户。如下图所示:

    小程序流程图.png

  9. 友好的界面

3 详细实现

3.1 需求1

需求1是基本需求,是整个设计的基石。因为没有用户信息就无法找到失主。主要涉及页面minemy-info以及全局js文件app.js

先说一下app.js,如果用户已经授权小程序获取用户信息,它就会在小程序启动时自动获取用户信息(如昵称,性别,头像),然后通过调用云函数login获取_openid(每个微信账号唯一的标识符),最后将它们一起存储到全局变量app.globalData.user中。如下图所示:

全局变量.png

mine页面依赖全局变量app.globalData.user,它的头像、昵称和性别均从这个变量获得

my-info页面的头像区域同样使用的是全局变量app.globalData.user获取的登陆状态。这个页面主要提供查看和修改真实姓名和联系电话的功能

下面将一一展示各页面的代码

3.1.1 app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//app.js
function setUserInfo(userInfo) {
  /* 根据参数 userInfo 设置全局变量 globalData.user,并更新其中的 openid 字段 */
  let app = getApp()
  app.globalData.user.hasLogin = true
  app.globalData.user.userInfo = userInfo
  // 调用云函数获取并设置 openid
  wx.cloud.callFunction({
    name: 'login',
    data: {},
    success: res => {
      console.log('[云函数] [login] user openid: ', res.result.openid)
      app.globalData.user.openid = res.result.openid
    },
    fail: err => {
      console.error('[云函数] [login] 调用失败', err)
    }
  })
}
App({
  onLaunch: function() {
    //判断是否支持云开发
    if (!wx.cloud) {
      console.error('请使用 2.2.3 或以上的基础库以使用云能力')
    } else {
      wx.cloud.init({
        traceUser: true,
      })
    }
    wx.getSetting({
      success: res => {
        // console.log(res)
        //如果用户已经授权,则获取相应的用户信息
        if (res.authSetting['scope.userInfo']) {
          wx.getUserInfo({
            success: res => {
              setUserInfo(res.userInfo)
            }
          })
        }
      }
    })
  },
  //全局变量
  globalData: {
    user: {
      hasLogin: false,
      openid: "",
      gender: ['unknown', 'nan','nvsheng'],
      userInfo: {
        avatarUrl: '/images/avatar.png',
        nickName: '点击头像登录',
      }
    }
  }
})

3.1.2 mine页面

mine页面.png

3.1.2.1 mine.wxml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--pages/mine/mine.wxml-->
<view class="page-body">
  <view class="page-section">
    <view class="page-body-info">
      <block>
        <button open-type="getUserInfo" class="control" plain="true" style="background-image:url('');" bindgetuserinfo="getUserInfo">
        </button>
        <text class="userinfo-nickname"> <text class='iconfont icon-'></text></text>
      </block>
    </view>
   <button bindtap='getMyInfo'>我的资料</button>
    <button bindtap='getMyFound'>捡到</button>
    <button bindtap='getMyLost'>丢失</button>
  </view>
</view>
3.1.2.2 mine.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
function islogged() {
  /* 判断是否登录并弹出错误提示 */
  let app = getApp()
  if (app.globalData.user.hasLogin) {
    return true
  } else {
    wx.showToast({
      title: '请先登录 ^_^',
      icon: 'none',
      duration: 1000
    })
    return false
  }
}
Page({
  data: {
    gender: [],
    userInfo: {}
  },
  onLoad() {},
  onShow() {
    //从全局变量获得用户信息
    let app = getApp()
    this.setData({
      gender: app.globalData.user.gender,
      userInfo: app.globalData.user.userInfo
    })
    // console.log(app.globalData.user.userInfo)
  },
  getMyInfo() {
    /* 跳转到我的资料页面(my-info) */
    if (islogged()) {
      wx.navigateTo({
        url: 'my-info/my-info',
        fail: res => {
          console.log(res)
        }
      })
    }
  },
  getUserInfo: function(e) {
    /* 在用户首次使用小程序时用于授权并获取用户信息 */
    console.log(e)
    let app = getApp()
    let userInfo=e.detail.userInfo
    if (app.globalData.user.hasLogin === false && userInfo) {
      app.globalData.user.hasLogin = true
      app.globalData.user.userInfo = userInfo
      that.setData({
        userInfo
      })
      // 调用云函数获取_openid
      wx.cloud.callFunction({
        name: 'login',
        data: {},
        success: res => {
          console.log('[云函数] [login] user openid: ', res.result.openid)
          app.globalData.user.openid = res.result.openid
        },
        fail: err => {
          console.error('[云函数] [login] 调用失败', err)
        }
      })
    }
  },
  getMyLost() {
    //跳转到 my-foundorlost 页面
    if (islogged()) {
      wx.navigateTo({
        url: '/pages/mine/my-foundorlost/my-foundorlost?is_lost=1',
        fail: function(res) {
          console.log("fail: " + res)
        }
      })
    }
  },
  getMyFound() {
    //跳转到 my-foundorlost 页面
    if (islogged()) {
      wx.navigateTo({
        url: '/pages/mine/my-foundorlost/my-foundorlost?is_lost=0',
        fail: function(res) {
          console.log("fail: " + res)
        }
      })
    }
  }
})

3.1.3 my-info页面

my-info页面.png

3.1.3.1 my-info.wxml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--pages/mine/mine.wxml-->
<view class="page-body">
  <view class="page-section">
    <view class="page-body-info">
      <block>
        <button class="control" plain="true" style="background-image:url('');">
        </button>
        <text class="userinfo-nickname"> <text class='iconfont icon-'></text></text>
      </block>
    </view>

    <form bindsubmit='postUserInfo'>
      <label>
        <text>真实姓名</text>
        <input name="real_name" disabled="" class="weui-input" maxlength="10" placeholder="请输入你的真实姓名" value='' />
      </label>
      <label>
        <text>手机号</text>
        <input name="phone_number" disabled='' class="weui-input" type='number' maxlength="11" placeholder="请输入你的真实电话号码" value='' />
      </label>
      <button type='primary' form-type='submit' wx:if="">保存</button>
      <button type='primary' bindtap='undisable' wx:if="">修改</button>
    </form>
  </view>
</view>
3.1.3.2 my-info.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// pages/mine/my-info/my-info.js
Page({
  data: {
    gender: [],
    userInfo: {},
    real_name: '',
    phone_number: '',
    is_disabled: true
  },
  onShow() {
    /* 获取用户的公开信息和登记的联系信息(真实姓名和联系电话) */
    let that = this
    let app = getApp()
    this.setData({
      gender: app.globalData.user.gender,
      userInfo: app.globalData.user.userInfo
    })
    const db = wx.cloud.database({
      env: "lost-and-found-22e624"
    })
    db.collection('users').where({
      _openid: app.globalData.user.openid
    }).get({
      success: res => {
        console.log(res)
        if (res.data.length == 1) {
          that.setData({
            real_name: res.data[0].real_name,
            phone_number: res.data[0].phone_number
          })
        } else {
          this.setData({
            is_disabled: false
          })
        }
      }
    })
  },
  postUserInfo(e) {
    /* 提交用户的联系信息 */
    const app = getApp()
    const result = e.detail.value
    // console.log(result)
    const db = wx.cloud.database({
      env: "lost-and-found-22e624"
    })
    let that = this
    db.collection('users').where({
      _openid: app.globalData.user.openid
    }).get({
      success: e => {
        // console.log(e)
        //如果数据库中没有则添加
        if (e.data.length == 0) {
          db.collection('users').add({
            data: {
              real_name: result.real_name,
              phone_number: result.phone_number,
              nickname: app.globalData.user.userInfo.nickName
            },
            fail: e => {
              console.log(e)
            },
            success: res => {
              wx.showToast({
                title: '保存成功!'
              })
              that.setData({
                is_disabled: true
              })
            }
          })
        } else if (e.data.length == 1) {
          //如果数据库中有则更新
          db.collection("users").doc(e.data[0]._id).update({
            data: {
              real_name: result.real_name,
              phone_number: result.phone_number
            },
            success: res => {
              wx.showToast({
                title: '保存成功!'
              })
              that.setData({
                is_disabled: true
              })
            },
            fail: res => {
              console.log(res)
            }
          })
        }
      },
      fail: e => {
        console.log(e)
      }
    })
  },
  undisable() {
    this.setData({
      is_disabled: false
    })
  }

})

3.2 需求2

对于第二个需求(能登记丢失和捡到的物品,别人可以查询到登记的信息),主要通过云开发中的数据库实现的。即将丢失和捡到的物品信息及相关的用户信息在登记后储存到云数据库中,每当显示查询页面时就从云数据库获取相应的数据并显示。所以主要涉及表单的提交和数据库的查询操作,下面将一一讲解

3.2.1 表单处理——release页面

release页面.png

3.2.1.1 release.wxml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<!--pages/release/release.wxml-->
<button bindtap='setLost'>丢失</button>
<button bindtap='setFound'>捡到</button>

<form bindsubmit="formSubmit" bindreset="formReset">
  <label>
    <text>显示标题</text>
    <input name="title" class="weui-input" maxlength="15" placeholder="如:可爱的一卡通" bindblur="guessExactName" />
  </label>
  <label>
    <text>准确名称(会根据显示标题猜测)</text>
    <picker name="exact_name" bindchange="bindPickerChange" value="" range="">
      <view class="weui-input" mode="selector"></view>
    </picker>
  </label>
  <view>
    <view>详细信息:</view>
    <label wx:if="">
      <text space='nbsp'>    学号</text>
      <input name="student_id" class="weui-input" type="number" placeholder="一卡通或学生证上的学号" />
    </label>
    <label wx:elif="">
      <text space='nbsp'>    书名</text>
      <input name="bookname" class="weui-input" type="text" placeholder="书本的名字" />
      <text space='nbsp'>    描述</text>
      <textarea auto-height="true" name="description" placeholder='对该物品的描述'></textarea>
    </label>
    <label wx:elif="">
      <text space='nbsp'>    身份证号</text>
      <input name="idcard_id" class="weui-input" type="idcard" placeholder="选填:不填将根据真实姓名匹配" />
    </label>
    <label wx:elif="">
      <text space='nbsp'>    型号</text>
      <input name="product" class="weui-input" type="number" placeholder="手机型号" />
      <text space='nbsp'>    描述</text>
      <textarea auto-height="true" name="description" placeholder='对该物品的描述'></textarea>
    </label>
    <label wx:else>
      <text space='nbsp'>    描述</text>
      <textarea auto-height='true' name="description" placeholder='对该物品的描述'></textarea>
    </label>
  </view>
  <view class="weui-cells" wx:if="">
    <view>上传图片</view>
    <view class="weui-cell">
      <view class="weui-cell__bd">
        <view class="weui-uploader">
          <view class="weui-uploader__hd">
            <view class="weui-uploader  __title">点击可预览选好的图片</view>
          </view>
          <view class="weui-uploader__bd">
            <view class="weui-uploader__files">
              <view class="weui-uploader__file">
                <image class="weui-uploader__img" src="" data-src="" bindtap="previewImage"></image>
              </view>
            </view>
            <view class="weui-uploader__input-box">
              <view class="weui-uploader__input" bindtap="chooseImage"></view>
            </view>
          </view>
        </view>
      </view>
    </view>
  </view>
  <view>记得时间和地点?
    <text bindtap='setDatePlace'>点这里</text>
  </view>
  <block wx:if="">
    <view>
      <text>时间</text>
      <picker name="date" mode="date" value="" start="2018-01-01" end="2050-12-31" bindchange="bindDateChange">
        <text></text>
      </picker>
      <picker name="time" mode="time" value="" start="00:00" end="11:59" bindchange="bindTimeChange">
        <text></text>
      </picker>
    </view>
    <label>
      <text>地点</text>
      <input name="place" class="weui-input" type="text" placeholder="地点" />
    </label>

  </block>
  <button type='primary' form-type='submit'>提交</button>
  <button type='default' form-type='reset'>重置</button>

</form>
3.2.1.2 release.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// pages/release/release.js

function randomString(len, charSet) {
  charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  var randomString = '';
  for (var i = 0; i < len; i++) {
    var randomPoz = Math.floor(Math.random() * charSet.length);
    randomString += charSet[randomPoz];
  }
  return randomString;
}
Page({

  /**
   * 页面的初始数据
   */
  data: {
    index: 1,
    array: ['一卡通', '钥匙', '水杯', '眼镜', '书', '身份证', '学生证', '手机', '其它'],
    imageTempPath: '',
    date: '',
    time: '',
    is_lost: true,
    date_place: false
  },
  setLost() {
    this.setData({
      is_lost: true
    })
  },
  setFound() {
    this.setData({
      is_lost: false
    })
  },
  bindPickerChange(e) {
    // console.log('picker发送选择改变,携带值为', e.detail.value)
    this.setData({
      index: e.detail.value
    })
  },
  bindDateChange(e) {
    this.setData({
      date: e.detail.value
    })
  },
  bindTimeChange(e) {
    this.setData({
      time: e.detail.value
    })
  },
  guessExactName(e) {
    let array = this.data.array
    let array_len = array.length
    for (let i = 0; i < array_len; i++) {
      if (e.detail.value.indexOf(array[i]) > -1) {
        this.setData({
          index: i
        })
        break
      }
    }

  },
  setDatePlace() {
    /* 用于设置是否显示时间地点 */
    let my_date = new Date()
    let date = my_date.getFullYear() + '-' + my_date.getMonth() + '-' + my_date.getDate()
    let time = my_date.getHours() + ':' + my_date.getMinutes()
    if (this.data.date_place == false) {
      this.setData({
        date_place: true,
        date,
        time
      })
    } else {
      this.setData({
        date_place: false,
        date: '',
        time: ''
      })
    }
  },
  previewImage(e) {
    /* 预览图片 */
    const current = e.target.dataset.src

    wx.previewImage({
      current,
      urls: this.data.imageTempPath
    })
  },
  chooseImage() {
    /* 选择图片 */
    const that = this
    wx.chooseImage({
      sourceType: ['camera', 'album'],
      sizeType: ['compressed', 'original'],
      count: 1,
      success(res) {
        // console.log(res)
        that.setData({
          imageTempPath: res.tempFilePaths[0]
        })
        console.log(that.data.imageTempPath)
      }
    })
  },
  formSubmit(e) {
    /*表单点击提交后的操作*/
    const result = e.detail.value
    const that = this
    console.log(result)

    let detailed_info = 0
    //根据物品的准确名称来确定需要的字段
    switch (result.exact_name) {
      case 0:
      case 6:
        if (result.student_id) {
          detailed_info = {
            student_id: result.student_id
          }
        }
        break;
      case 4:
        if (result.bookname || result.description) {
          detailed_info = {
            bookname: result.bookname,
            description: result.description
          }
        }
        break;
      case 5:
        if (result.idcard_id) {
          detailed_info = {
            idcard_id: result.idcard_id
          }
        }
        break
      case 7:
        if (result.description || result.product) {
          detailed_info = {
            product: result.product,
            description: result.description
          }
        }
        break;
      default:
        if (result.description) {
          detailed_info = {
            description: result.description
          }
        }
        break;
    }
    // console.log(detailed_info)
    if (result.title == '' || detailed_info == 0) {
      wx.showToast({
        title: '请填完表单后再提交',
        icon: 'none',
        duration: 2000
      })
      return
    }
    //上传图片
    let cloudPath = ''
    if (this.data.imageTempPath != '') {
      let temp = this.data.imageTempPath.split(".")
      cloudPath = randomString(8) + "." + temp[temp.length - 1]
      // console.log(result, cloudPath)
      wx.cloud.uploadFile({
        cloudPath: cloudPath,
        filePath: this.data.imageTempPath,
        fail: (res) => {
          console.log(res);
        }
      })
    }
    const db = wx.cloud.database({
      env: "lost-and-found-22e624"
    })
    const app = getApp()
    const collection_name = (this.data.is_lost ? 'lost' : 'found')
    let current_date = new Date()
    current_date.setDate(current_date.getDate()-3)
    // console.log(app.globalData.user.openid)
    db.collection(collection_name).where({
      title: result.title,
      exact_name: this.data.array[result.exact_name],
      detailed_info: detailed_info,
      _openid: app.globalData.user.openid,
      release_date: db.command.gt(current_date) //判断是否在三天内提交过重复的内容
    }).get({
      success: res => {
          // console.log(res.data)
        if (res.data.length > 0) {
          wx.showToast({
            title: '你在过去的三天内已经提交过相同内容',
            icon: 'none',
            duration: 2000
          })
        } else {
          let datetime={date:result.date,time:result.time}
          console.log("db.add ing.....")
          //提交数据
          db.collection(collection_name).add({
            data: {
              exact_name: that.data.array[result.exact_name],
              title: result.title,
              datetime,
              place: result.place,
              release_date: new Date(),
              picture: cloudPath,
              detailed_info
            },
            fail: res => {
              console.log(res)
            },
            success: res => {
              wx.showToast({
                title: '提交成功!',
                duration: 2000
              })
            }
          })
        }
      }
    })
  }
})

3.2.2 数据查询——find页面

find页面.png

3.2.2.1 find.wxml
1
2
3
4
5
6
7
8
9
10
11
12
<!--pages/find/find.wxml-->
<button bindtap='setFound'>捡到</button>
<button bindtap='setLost'>丢失</button>
<view wx:for="" wx:key="*item">
  <navigator url='detailed-info/detailed-info?index='>
    <text></text>
    <text></text>
    <text></text>
    <text></text>
    <text></text>
  </navigator>
</view>
3.2.2.2 find.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// pages/find/find.js
Page({
  onShow: function () {
    const that=this
    const db = wx.cloud.database({ env: "lost-and-found-22e624" })
    const collection_name=(this.data.is_lost?'lost':'found')
    db.collection(collection_name).limit(20).get({
      success(res) {
        // res.data 包含该记录的数据
        that.setData({
          display: res.data
        })
        console.log(that.data.display)
      },
      fail(res){
        console.log(res)
      }
    })
  },
  setLost(){
    const that = this
    const db = wx.cloud.database({ env: "lost-and-found-22e624" })
    this.setData({
      is_lost: true
    })
    db.collection('lost').limit(20).get({
      success(res) {
        // res.data 包含该记录的数据
        that.setData({
          display: res.data
        })
        console.log(that.data.display)
      },
      fail(res) {
        console.log(res)
      }
    })
  },
  setFound() {
    const that = this
    const db = wx.cloud.database({ env: "lost-and-found-22e624" })
    this.setData({
      is_lost: false
    })
    db.collection('found').limit(20).get({
      success(res) {
        // res.data 包含该记录的数据
        that.setData({
          display: res.data
        })
        console.log(that.data.display)
      },
      fail(res) {
        console.log(res)
      }
    })
  }
})

3.3 需求3

需求3是:针对不同常见物品设置相应的关键字段(如一卡通为学号),在字段为空时自动不显示(如一卡通不需要图片和时间地点信息就能百分百匹配成功)。从而避免对于每个物品都要填写固定的字段,改善了用户体验(显示时也会自动不显示未填写的字段)。

这个需求通过 WXML 文件中的wx:if结合页面变量(data)来实现。这个需求主要涉及两个页面:releasedetailed-info。前者即发布页面,只提供必要的字段给用户填写,尽可能节省用户时间,且可以根据显示标题猜测准确名称,以使用户几乎不需要花什么时间就可以提交一个登记(捡到或丢失)信息;后者则是物品详情页面,它也会自动不显示不必要的字段信息

3.3.1 release页面

3.3.1.1 release.wxml

参见3.1.1.1 release.wxml

3.3.1.2 release.js

参见3.1.1.2 release.js

3.3.2 detailed-info页面

detailed-info页面.png

3.3.2.1 detailed-info.wxml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!--pages/find/detailed-info/detailed-info.wxml-->
<view>
  <view style='text-align:center'>  </view>
  <view>发布者:</view>
  <view>联系电话:</view>
  <view>发布时间:</view>
  <view>准确名称:</view>
  <view wx:if="">
    <view>时间: </view>
    <view>地点:</view>
  </view>
  <view>详细信息:
    <view wx:if="">
      <text space="nbsp">      学号:</text> 
    </view>
    <view wx:elif="">
      <text space="nbsp">      身份证号:</text> 
    </view>

    <view wx:elif="">
      <text space="nbsp">      书名:</text> 
      <text space="nbsp">      描述:</text> 
    </view>
    <view wx:else>
      <text space="nbsp">      描述:</text> 
    </view>
    
    <view wx:if=""><text space='nbsp'>      图片:</text> 
      <image mode="aspectFit" lazy-load='true' src="" bindtap="previewImage"></image>
    </view>
  </view>
</view>
3.3.2.2 detailed-info.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// pages/find/detailed-info/detailed-info.js

Page({

  /**
   * 页面的初始数据
   */
  data: {
    index: 0,
    item: {},
    user: {},
    hasPicture:false
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
    this.setData({
      index: options.index
    })
  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function() {
    //获取上一个页面记录的数组下标
    const pages = getCurrentPages();
    let item = pages[pages.length - 2].data.display[this.data.index]; //上一个页面
    // const is_lost = pages[pages.length - 2].data.is_lost; //上一个页面
    let that = this
    let t = new Date(item.release_date)
    // console.log(item.place)
    item.release_date = t.getFullYear()+'-'+t.getMonth()+'-'+t.getDate()+' '+t.getHours()+':'+t.getMinutes()
    //使用上一页面获取到的信息设置要显示的详细信息(从而不用再次访问数据库)
    this.setData({
      item
    })
    //获取相应的用户信息
    const db = wx.cloud.database({
      env: "lost-and-found-22e624"
    })
    console.log(item._openid)
    db.collection("users").where({
      _openid: item._openid
    }).get({
      success(res) {
        console.log(res)
        that.setData({
          user: res.data[0]
        })
        // console.log(that.data.user)
      },
      fail(res) {
        console.log(res)
      }
    })
    //如果有图片将下载图片并显示
    if (item.picture) {
      wx.cloud.downloadFile({
        fileID: 'cloud://lost-and-found-22e624.6c6f-lost-and-found-22e624/' + item.picture,
        success: res => {
          // console.log(res)
          that.setData({
            'item.picture': res.tempFilePath,
            hasPicture:true
          })
          // console.log(that.data.item.picture)

        },
        fail: res => {
          console.log(res)
        }
      })
    }
  },
  previewImage(e) {
    // console.log(e)
    const current = e.target.dataset.src
    let temp = []
    temp[0] = this.data.item.picture
    wx.previewImage({
      current,
      urls: temp
    })
  },
})

3.4 需求4

需求4的内容是:可以查看自己已经发布的捡到或丢失信息,并可以进行修改(即重发布)。

涉及到的页面有mine页面(主要体现在有两个按钮——捡到和丢失,用于跳转到相应的页面)、my-foundorlost页面(和find页面非常相似)以及mine/detailed-info页面(注意和find/detailed-info页面区分)。

这部分需求尚未完全实现,只实现了“查看自己已经发布的捡到或丢失信息”这一部分,而这个问题的设计思想和find页面及find/detailed-info页面是几乎一样的,故在此不再赘述。

而另一部分——“并可以进行修改(即重发布)”则因为时间关系尚未实现。 相关代码和截屏由于和上述的find页面(3.2.2 数据查询——find页面)及find/detailed-info页面(3.3.2 detailed-info页面)几乎一致,就不贴了

3.5 其它需求

由于时间关系,其它需求已列入6 TODO清单,作归档处理,待日后完善。

4 演示结果

设计方案(即需求)已完成4/9,基本功能已经实现。已完成的需求已经经过调试,并经过内部人员测试。

小程序默认进入查询页面(find),根据用户选择进入相关的功能页面。

存在的主要问题为:界面不够美观,还有很多核心功能尚未实现,以及性能问题(即小程序的响应速度),安全和隐私问题,代码的优化问题。详见6 TODO清单

至于截屏可查看上一节详细实现中的各个页面开始的部分(如3.2.2 数据查询——find页面)

5 心得体会

此次的课程设计——做一个解决校内问题的微信小程序,让我早早地兴奋起来了(开学听了这个消息之后就异常激动),因为它看起来是那么的有趣。于是开学第一个月我就把差不多整整一个月的时间花在了前端学习上(因为感觉后面好多东西都需要前端的知识,如Web安全),主要是指HTML、CSS、JavaScript。由于期间依然在上课,所以真正可用的时间并没有一个月那么多。学了基本的Web前端知识后,我只写了个刷网课的JS脚本就没有再管了。

后面事情越来越多,再也没有碰过相关的东西

直到最后几个周,deadline将至,才开始学习小程序的各种东西并着手开始这个“西电失物招领”小程序。在花了不少时间后才完成到这种程度 -_-

本以为这是个很简单的事情,因为小程序嘛,我们每天都在用,功能和界面也就那一些,并且我有学过Web前端的东西,而小程序和Web前端还是很类似的,所以充满信心,更是想着把所有想到的需求全部实现。但是真到开始写的时候,就尴尬了/笑哭。

先是在学习小程序的基本框架(官方基础示例——云开发Quick Start,小程序开发官方文档)上就花了不少时间,而后简单了解各个组件的使用(官方给的样例——微信小程序示例)也花了不少时间,而真正开始写自己的这个小程序时,更是写到怀疑人生。各种想实现的操作不知道如何实现(如用全局变量来保存登录状态这个主意我之前就没想到),尤其有一次大碰壁,让我自闭了两天/笑哭。好在努力之后,终于勉强实现了基本功能

设计中最让我欣赏的部分莫过于自动通知(即需求8,不过还没实现就是了)。即在有捡到的用户登记后,如果确定已有的丢失与之匹配,则及时通知丢东西的用户;反之,若在丢失的用户登记后,如果之前有人登记了相关的捡到信息,则会通知捡到东西的用户。然后针对不同常见物品设置相应的关键字段也是我满意的地方。当然,还有那个方便的搜索功能。不过以上都还没有实现/尴尬

技术上倒没有什么得意之处,因为确实好多地方都不尽人意 不足之处倒是很多,不过4 演示结果和6 TODO清单中已经说得很详细了,这里就不赘述了

6 TODO清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- [ ] 1 界面美化
      - [ ] 1.1 各页面的 .json 文件
            主要需要修改各个页面的标题(毕竟各个页面的标题应该不一样)
      - [ ] 1.2 查询和发布中的丢失和捡到按钮
            至少要放到同一行,并且能显示是否被选中
      - [ ] 1.3 其它页面美化
            - [ ] 查询页面(`pages/find/find`)
            - [ ] 发布页面(`pages/release/release`)
            - [ ] (可选)我的页面(`pages/mine/mine`)
            - [ ] (可选)我的页面中的我的资料页面(`pages/mine/my-info/my-info`)
            - [ ] 我的页面中的捡到和丢失页面(`pages/mine/my-foundorlost/my-foundorlost`)
            - [ ] 查询页面跳转到的详细信息页面(`pages/find/detailed-info/detailed-info`)
            - [ ] 我的页面中的捡到或丢失跳转到的详细信息页面(`pages/mine/detailed-info/detailed-info`)
- [ ] 2 功能
      - [ ]  2.1 查询页面添加搜索按钮
          并完成相关云函数及调用代码
      - [ ]  2.2 发布完成后自动匹配并询问用户是否跳转到可能的结果页面
          完成相关的云函数及调用代码
      - [ ]  2.3 (可选)补充物品类型
      - [ ]  2.4 (可选)我的资料添加学号字段?
      - [ ]  2.5 我的->捡到(或丢失)中跳转到的详细页面中添加修改和匹配功能
      - [x]  2.6 关于发布表单
          - [x]  重复内容3天内不能添加(主要是指显示标题,准确名称,详细信息相同)
          - [x]  提示成功消息
          - [x]  未填完关键信息不能提交(如显示标题,准确名称,详细信息中的至少一项)
      - [ ]  2.7 东西找到后的处理
      - [ ]  2.8 我的->捡到(或丢失)->详细页面->发布日期显示格式问题
      - [ ]  2.9 发布页面的日期问题(不能添加今天之后日期做为丢失或捡到日期)
- [ ] 3 性能优化?
      - [ ]  3.1 方法一:将常用的数据在加载app时直接下载到全局变量中?
- [ ] 4 安全和隐私
      - [ ]  4.1 完善发布表单和我的资料表单
          如限制用户发布次数、修改次数(包括每天和每月)
- [ ] 5 代码优化
      健壮性,可维护性,可拓展性

7 参考文献

链接

下面将罗列写小程序过程中可能会用到的链接: