1.背景
我校的素拓分统计流程十分繁琐,除了可以借助报名工具供学生报名活动外,其他素拓分统计操作几乎全部由人工完成,效率低下,出错率较高,在学生学业繁重的形势下,我校亟需诞生一个从活动发布到统计学分的自动化高效平台。
为此,本次项目设计一个基于微信小程序云开发的素拓分管理系统,可以实现线上发布活动,自动统计报名名单,通过扫码签到、签退方式验证学生是否完成活动和自动为学生统计加分等,致力于减轻我校素拓分统计人员的负担。
2. 功能介绍
2.1 用户模块
小程序分为三种用户:普通学生用户、活动发布者和系统管理员。用户登录小程序默认为普通用户,系统管理员可将普通用户设置为活动发布者,也可以将活动发布者撤销权限为普通用户。
2.2 发布活动
活动发布者和系统管理员可以发布活动。编辑活动未发布退出后,会保存草稿;编辑完成后,可以预览活动发布;待活动编辑无误后,可发布活动。活动发布后,由于采用集合监听形式,其他用户能实时接收活动数据。
集合监听代码如下:
onChange: async (snapshot) => {
console.log("监听活动表", snapshot)
// 遍历,正在进行的活动(根据AcStartTime>now,AcNum>0),存入this.data
// 1. 获取当前网络时间
let now = await asyncWx.cloudFunction({
name: "getNetworkTime"
})
console.log("当前网络时间", now)
// 查找所有存活的活动
let AllList = snapshot.docs.filter((v) =>
(v.rest == 'infinite' || v.rest > 0) && parseInt(v.activityObj.AcStartTime) > parseInt(now)
)
console.log("所有存活的活动", AllList)
// 分类存活的所有活动
let categoryArr = [];
// 第一层为 全部活动
categoryArr.unshift(AllList.reverse())
this.data.tabs.forEach((v1, i) => {
if (i > 0) {
categoryArr[i] = AllList.filter(v2 => {
return v2.activityObj.AcScoreType == v1
}).reverse()
}
})
// 存入缓存
wx.setStorageSync('liveActivities', AllList)
// 为页面赋值
this.setData({ categoryArr })
wx.hideLoading({
success: (res) => { },
})
},
2.3 生成打卡码
活动发布后,可以生成签到码和签退码,用户扫描二维码会跳转到打卡页面,生成二维码代码如下:
云函数代码:
exports.main = async (event, context) => {
try {
const result = await await cloud.openapi.wxacode.get({
path:event.path,
width:320
})
let qrName = `QR_${new Date().getTime()}.jpg`
const file = await cloud.uploadFile({
cloudPath: qrName,
fileContent: result.buffer,
})
return file
} catch (err) {
return err
}
}
js代码:
let signInQR = await asyncWx.cloudFunction({
name: "createQRCode",
data: {
path: "pages/verify/verify?type=signIn&AcId=" + id
}
})
2.4 打卡验证
根据二维码参数,验证是签到还是签退,代码如下:
async clockIn() {
wx.showLoading({
title: '加载中...',
mask: true
})
if (openid == "") {
wx.hideLoading({
success: (res) => { },
})
asyncWx.showToast({ title: "尚未登录!" })
return;
}
// 验证用户资料是否完善
if (JSON.stringify(userBaseInfo) == "{}") {
wx.hideLoading({
success: (res) => { },
})
asyncWx.showToast({ title: "尚未完善资料!" })
return;
}
// 验证用户是否在指定地点
// 获取自身位置
// 1. 判断是否授权
try {
let res = await asyncWx.authorize("scope.userLocation")
if (res !== false) {
let location = await asyncWx.authorizeByType("getLocation")
console.log("当前位置是:", location)
let longitudeNow = location.longitude;
let latitudeNow = location.latitude;
// 打卡位置
let { latitude, longitude } = this.data.clockInLocation
console.log("打卡地点位置为", latitude, longitude)
// 计算距离
let dis = distance(latitudeNow, longitudeNow, latitude, longitude)
console.log(dis)
if (dis > 200) {
wx.hideLoading({
success: (res) => { },
})
await asyncWx.showModal({ content: "超出打卡范围,请导航移至打卡范围200米以内" })
return;
}
} else {
wx.hideLoading({
success: (res) => { },
})
return;
}
} catch (e) {
wx.hideLoading({
success: (res) => { },
})
console.log(e)
return;
}
//通过了上面开始操作数据库
// 数据库操作用到的数据
console.log("AcId", AcId, "openid", openid, "type", type)
if (type == "signIn") { //如果是签到
console.log("签到")
// 获取网路当前时间
let nowTime = await asyncWx.cloudFunction({
name: "getNetworkTime"
})
console.log("当前网络时间", nowTime)
if (nowTime > this.data.AcStartTime) { //如果超时
// 修改STF_Participate用户状态为fail
let updateRes1 = await asyncWx.cloudFunction({
name: "databaseOperate",
data: {
type: "updateAttribute1",
collection: "STF_Participate",
myWhere: {
AcId
},
field: "participateUsers",
attribute: openid,
attributeValue: "fail"
}
})
// 修改STF_Users参加的活动状态为fail
let updateRes2 = await asyncWx.cloudFunction({
name: "databaseOperate",
data: {
type: "updateAttribute1",
collection: "STF_Users",
myWhere: {
_openid: openid
},
field: "userJoinActivities",
attribute: AcId,
attributeValue: "fail"
}
})
// 修改按钮状态
this.setData({
btnValue:"已失效",
isBtnDisabled:true
})
wx.hideLoading({
success: (res) => { },
})
await asyncWx.showModal({ content: "签到超时,活动实效!" })
return;
} else {
// 修改STF_Participate用户状态为signIn
let updateRes1 = await asyncWx.cloudFunction({
name: "databaseOperate",
data: {
type: "updateAttribute1",
collection: "STF_Participate",
myWhere: {
AcId
},
field: "participateUsers",
attribute: openid,
attributeValue: "signIn"
}
})
// 修改按钮状态
this.setData({
btnValue:"已签到",
isBtnDisabled:true
})
wx.hideLoading({
success: (res) => { },
})
await asyncWx.showModal({ content: "签到成功!" })
return;
}
}
if (type == "signOut") { //如果是签退
console.log("签退")
// 获取网路当前时间
let nowTime = await asyncWx.cloudFunction({
name: "getNetworkTime"
})
console.log("当前网络时间", nowTime)
if (nowTime > this.data.AcEndTime) { //如果超时
// 修改STF_Participate用户状态为fail
let updateRes1 = await asyncWx.cloudFunction({
name: "databaseOperate",
data: {
type: "updateAttribute1",
collection: "STF_Participate",
myWhere: {
AcId
},
field: "participateUsers",
attribute: openid,
attributeValue: "fail"
}
})
// 修改STF_Users参加的活动状态为fail
let updateRes2 = await asyncWx.cloudFunction({
name: "databaseOperate",
data: {
type: "updateAttribute1",
collection: "STF_Users",
myWhere: {
_openid: openid
},
field: "userJoinActivities",
attribute: AcId,
attributeValue: "fail"
}
})
// 修改按钮状态
this.setData({
btnValue:"已失效",
isBtnDisabled:true
})
wx.hideLoading({
success: (res) => { },
})
await asyncWx.showModal({ content: "签退超时,活动实效!" })
return;
} else {
// 修改STF_Participate用户状态为finished
let updateRes1 = await asyncWx.cloudFunction({
name: "databaseOperate",
data: {
type: "updateAttribute1",
collection: "STF_Participate",
myWhere: {
AcId
},
field: "participateUsers",
attribute: openid,
attributeValue: "finished"
}
})
// 修改STF_Users参加的活动状态为finished,同时加上学分
// 根据学分做出相对的加分
let scoreType = "";
switch (activityObj.AcScoreType) {
case "德育分":
scoreType = "dScore"
break;
case "智育分":
scoreType = "zScore"
break;
case "劳育分":
scoreType = "lScore"
break
case "体育分":
scoreType = "tScore"
break
case "美育分":
scoreType = "mScore"
break
default:
scoreType = "vScore"
break
}
let updateRes2 = await db.collection("STF_Users").where({
_openid: openid
}).update({
data: {
userJoinActivities: {
[AcId]: "finished",
},
scoreList: {
[scoreType]: _.inc(parseFloat(activityObj.AcScoreNum))
}
}
})
// 修改按钮状态
this.setData({
btnValue:"已完成",
isBtnDisabled:true
})
wx.hideLoading({
success: (res) => { },
})
await asyncWx.showModal({ content: "签退成功,活动完成!" })
return;
}
}
}
2.5 导出名单
这里云函数需要安装node-xlsx类库,代码如下:
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
env:cloud.DYNAMIC_CURRENT_ENV
})
// 重点:一定要 npm install node-xlsx
//操作excel用的类库
const xlsx = require('node-xlsx');
// 云函数入口函数
exports.main = async(event, context) => {
try {
//1 处理excel表格名
let excelName = event.excelName;
excelName += `_${new Date().getTime()}`
excelName += '.xlsx'
//2 把数据保存到excel里
var buffer = await xlsx.build([{
name: event.excelName,
data: event.data
}]);
//4,把excel文件保存到云存储里, 不用success,会直接返回fileID
return await cloud.uploadFile({
cloudPath: excelName,
fileContent: buffer, //excel二进制文件
})
} catch (e) {
console.error(e)
return e
}
}
3. 视频演示
视频链接:https://v.qq.com/x/page/y31511jj6pt.html?
我们团队想要开发一个小程序,可以合作吗?如可以合作,欢迎加微信:mfj-1991-rsf
额,以前还不知道这个简称,素质拓展么