小组成员:(组长:周慧微;组员:曹洪茹、杨亮、陆祈桢、王圣奇)
小组Git链接:https://github.com/zhouhuiwei/weChat
项目要求:(10个基本功能+1个附加功能)
做成微信小程序,强化社交功能(参考跳一跳)。
1、 利用微信账号进行用户管理
2、 小程序分无尽模式和考试模式(娱乐和练习),无尽模式可以选择题目困难等级(简单/中等/困难)
3、 无尽模式得分模式设计:答对得一分,连答分数与连续答对次数相同,答错停止答题,显示得分
4、 无尽模式参与微信好友排名
5、 考试模式用户自由选择运算符(类型和个数)以及出题数目
6、 考试模式可以设置时间限制
7、 每套考试题结束后出现统计页面,显示正确率
8、 可以打印一套试卷,导出(复制黏贴)到微信小程序之外
9、 可以从程序内一键分享到微信聊天框;分享内容为无尽模式的得分或考试模式的正确率
10、 无尽模式一周清空排名,重新记录,同时保留历史最高成绩
*附加:程序界面支持多种语言
【注】小程序自身限制,无法直接分享到朋友圈等,故功能9已经稍作修改。
项目进度安排:
2月10号之前交第一版,实现无尽模式和考试模式
——更新:在GitHub链接里的压缩包部分
2月20号之前交第二版,修改第一版意见,并增加分享和导出试卷,排名功能。
——更新:用户调查:没有发现bug,能够正常实现功能。
2月25日前修改第二版意见,提交最终稿
——更新:将无尽模式得分方式进行修改:
简单模式对一题累计1分,中等模式对一题累计得2分,困难模式对一题累计3分。
人员分工 :
功能点分配如下:
1、3、4:杨亮、曹洪茹
5、7、8:周慧微
2、6:王圣奇
9、10:陆祈桢
【注】随时交流,多多调整
更新:工作量总结:
周慧微 | 22% |
杨亮 | 20% |
曹洪茹 | 20% |
王圣奇 | 19% |
陆祁桢 | 19% |
前后台负责人如下:
前台:周慧微
后台(用java编写):杨亮、曹洪茹
更新:
wxml是用html写,wxss是用css写,
js、服务端是用JavaScript写。
界面原型设计:
使用墨刀和微信小程序提供基础按钮设计
代码规范:
Java编码规范——Alibaba
使用阿里代码规范检查工具,《阿里巴巴Java开发规约》插件
编程都符合相应的软件开发规范。从软件工程课程中习得,这样方便后续维护修改,尤其是团队编程过程中极为重要,可以提高效率。
小程序页面设计+核心代码(包括部分功能的测试):
1、 功能点1_利用微信登录部分:
通过群内分享进入小程序的用户,将另外获取群ID并排名
// 登录 wx.login({ success: res => { // 发送 res.code 到后台换取 openId, sessionKey, unionId //获取openid wx.request({ url: that.globalData.host + "GetOpenId", data: res, method: 'POST', header: { 'content-type': "application/x-www-form-urlencoded" }, success: function (e) { console.log(e.data); that.globalData.secret = e.data; // 获取用户信息,并上传服务器 wx.getUserInfo({ success: res => { // 可以将 res 发送给后台解码出 unionId that.globalData.userInfo = res.userInfo //将用户信息openID,nickName,avatarUrl,上传服务器 let cs = {}; console.log("以下是globalData的数据:"); console.log(that.globalData); cs.nickName = that.globalData.userInfo.nickName; cs.openID = that.globalData.secret.openid; cs.avatarUrl = that.globalData.userInfo.avatarUrl; wx.request({ url: that.globalData.host + "InsertUserInfoszys", data: cs, method: 'POST', header: { 'content-type': "application/x-www-form-urlencoded" }, success: function (e) { console.log(e); } }); // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 // 所以此处加入 callback 以防止这种情况 if (this.userInfoReadyCallback) { this.userInfoReadyCallback(res) } //获取群id,前提是通过群排名进入小程序的用户,否则下面的步骤都不会执行 wx.getShareInfo({ shareTicket: options.shareTicket, success: function (e) { console.log(e); let cs = e; cs['session_key'] = that.globalData.secret.session_key; wx.request({ url: that.globalData.host + "JieMi", data: cs, method: 'POST', header: { 'content-type': "application/x-www-form-urlencoded" }, success: function (e) { console.log(e); that.globalData.openGId = e.data.openGId;//群id } }); } });//end of wx.getShareInfo } });//end of wx.getUserInfo } }); } });//end of wx.login
2、功能点2_小程序两种模式
【考试模式】 【无尽模式】 三档难度
计算式生成(没有重复题目),并且计算正确答案
const formatNumber = n => { n = n.toString() return n[1] ? n : '0' + n } function getNum(weishu) {//this.data.weishu //获取一个操作数 return Math.floor(Math.random() * Math.pow(10, weishu)); } function getYunsuanfu(yunsuanfu) { //获取运算符 var length = yunsuanfu.length;//this.data.yunsuanfu var mark = Math.floor(Math.random() * length); return yunsuanfu[mark]; } function getBiaodashi(yunsuanshu, yunsuanfu, weishu, timu, correct_result, xiaoshuwei) {//参与运算的数字个数this.data.yunsuanshu,参与运算的运算符数组this.data.yunsuanfu,参与运算的数字位数this.data.weishu,当前的题库this.data.timu,正确答案数组this.data.correct_result,除法答案保留的小数位this.data.xiaoshuwei //获取表达式 var biaodashi = ""; for (var i = 0; i < yunsuanshu; i++) { biaodashi = biaodashi + getNum(weishu) + getYunsuanfu(yunsuanfu); } biaodashi = biaodashi.slice(0, -1);//把多余的运算符取消掉 biaodashi = biaodashi + "=";//给表达式增加一个"=" console.log(biaodashi); var result = judge_biaodashi(biaodashi.slice(0, -1),timu); if (result === false) { return getBiaodashi(yunsuanshu, yunsuanfu, weishu, timu, correct_result, xiaoshuwei); } else { result = Math.round(result * Math.pow(10, xiaoshuwei)) / Math.pow(10, xiaoshuwei); correct_result.push(result); //this.setData({ correct_result: tmp }); //this.setData({ biaodashi: biaodashi }); timu.push(biaodashi); //this.setData({ timu: tm }); return {correct_result: correct_result,biaodashi: biaodashi, timu: timu}; } } function judge_biaodashi(biaodashi,timu) {//表达式本身,当前的题库 //验证表达式的结果是否正常,不能为负数,不能是Infinity,不能是NaN,不能超过设置的运算位数 //不能有重复题目 var result = calCommonExp(biaodashi); console.log(result); if (!isFinite(result)) { return false; } if (isNaN(result)) { return false; } if (result < 0) { return false; } if (timu.indexOf(biaodashi + "=") !== -1) {//不能有重复题目 return false; } return result; } //以下代码是框架的代码,借用一下rpn.js function isOperator(value) { var operatorString = '+-*/()×÷'; return operatorString.indexOf(value) > -1; } function getPrioraty(value) { if (value == '-' || value == '+') { return 1; } else if (value == '*' || value == '/' || value == '×' || value == '÷') { return 2; } else { return 0; } } function prioraty(v1, v2) { return getPrioraty(v1) <= getPrioraty(v2); } function outputRpn(exp) { var inputStack = []; var outputStack = []; var outputQueue = []; var firstIsOperator = false; exp.replace(/\s/g, ''); if (isOperator(exp[0])) { exp = exp.substring(1); firstIsOperator = true; } for (var i = 0, max = exp.length; i < max; i++) { if (!isOperator(exp[i]) && !isOperator(exp[i - 1]) && (i != 0)) { inputStack[inputStack.length - 1] = inputStack[inputStack.length - 1] + exp[i] + ''; } else { inputStack.push(exp[i]); } } if (firstIsOperator) { inputStack[0] = -inputStack[0] } while (inputStack.length > 0) { var cur = inputStack.shift(); if (isOperator(cur)) { if (cur == '(') { outputStack.push(cur); } else if (cur == ')') { var po = outputStack.pop(); while (po != '(' && outputStack.length > 0) { outputQueue.push(po); po = outputStack.pop(); } } else { while (prioraty(cur, outputStack[outputStack.length - 1]) && outputStack.length > 0) { outputQueue.push(outputStack.pop()); } outputStack.push(cur) } } else { outputQueue.push(Number(cur)); } } if (outputStack.length > 0) { while (outputStack.length > 0) { outputQueue.push(outputStack.pop()); } } return outputQueue; } function calRpnExp(rpnArr) { var stack = []; for (var i = 0, max = rpnArr.length; i < max; i++) { if (!isOperator(rpnArr[i])) { stack.push(rpnArr[i]); } else { var num1 = stack.pop(); var num2 = stack.pop(); if (rpnArr[i] == '-') { var num = num2 - num1; } else if (rpnArr[i] == '+') { var num = num2 + num1; } else if (rpnArr[i] == '*' || rpnArr[i] == '×') { var num = num2 * num1; } else if (rpnArr[i] == '/' || rpnArr[i] == '÷') { var num = num2 / num1; } stack.push(num); } } return stack[0]; } function calCommonExp(exp) { var rpnArr = outputRpn(exp); return calRpnExp(rpnArr) } function simplyBiaodashi(timu){ var sz = timu; var tm = sz[0].slice(0, -1); for (var i = 1; i < sz.length; i++) { tm = tm + "," + sz[i].slice(0, -1); } return tm;
界面设置
<!--index.wxml--> <view class="page" wx:if="{{isAuthority=='是'}}" xmlns:wx="http://www.w3.org/1999/xhtml"> <view class="kj"> <view class='title'>欢迎使用考试模式</view> <view class='shuoming'>*使用前,请先进行配置</view> <view class='peizhi'> <picker range="{{array_yunsuanshu}}" value="{{index_yunsuanshu}}" mode='selector' bindchange='yunsuanshuchanged'> <view class='peizhi-item'> <view class='peizhi-item-left'> <view>运算数</view> <view>参与运算的数字个数</view> </view> <view class='peizhi-item-middle'> <!-- <picker range="{{array_yunsuanshu}}" value="{{index_yunsuanshu}}" mode='selector'> --> <view class='picker'>{{array_yunsuanshu[index_yunsuanshu]}}个</view> <!-- </picker> --> </view> <view class='peizhi-item-right'><image style='width: 50rpx;height: 50rpx' src='/images/more.png'></image></view> </view> </picker> <view class='line'></view> <picker range="{{array_timushu}}" value="{{index_timushu}}" mode='selector' bindchange='timushuchanged'> <view class='peizhi-item'> <view class='peizhi-item-left'> <view>题目数量</view> <view>考试的题目数量</view> </view> <view class='peizhi-item-middle'> <view class='picker'>{{array_timushu[index_timushu]}}个</view> </view> <view class='peizhi-item-right'><image style='width: 50rpx;height: 50rpx' src='/images/more.png'></image></view> </view> </picker> <view class='line'></view> <picker range="{{array_weishu}}" value="{{index_weishu}}" mode='selector' bindchange='weishuchanged'> <view class='peizhi-item'> <view class='peizhi-item-left'> <view>位数</view> <view>参与运算的数字位数</view> </view> <view class='peizhi-item-middle'> <view class='picker'>{{array_weishu[index_weishu]}}位</view> </view> <view class='peizhi-item-right'><image style='width: 50rpx;height: 50rpx' src='/images/more.png'></image></view> </view> </picker> <view class='line'></view> <view class='peizhi-item' bindtap='yunsuanfu_change'> <view class='peizhi-item-left'> <view>运算符</view> <view>参与运算的运算符</view> </view> <view class='peizhi-item-middle'> {{array_yunsuanfu_result}} </view> <view class='peizhi-item-right'><image style='width: 50rpx;height: 50rpx' src='/images/more.png'></image></view> </view> <view class='line'></view> <picker range="{{array_time}}" value="{{index_time}}" mode='selector' bindchange='timechanged'> <view class='peizhi-item'> <view class='peizhi-item-left'> <view>考试时间</view> <view>设置考试所用时间</view> </view> <view class='peizhi-item-middle'> <view class='picker'>{{array_time[index_time]}}分钟</view> </view> <view class='peizhi-item-right'><image style='width: 50rpx;height: 50rpx' src='/images/more.png'></image></view> </view> </picker> <view class='line'></view> <view class='peizhi-item'> <view class='peizhi-item-left'> <view>结果只保留整数</view> <view>计算结果为小数,四舍五入得到整数</view> </view> <view class='peizhi-item-right peizhi-item-right2'><!-- 这里增加了一个类名,因为与上面的布局不同,上面的布局,主轴上有三个元素,宽度是80%,10%,10%,而现在这个布局,主轴上只有两个元素,宽度是80%,20% --> <switch checked='{{switchState}}' bindchange='switchChange'></switch> </view> </view> <view class='line'></view> <picker range="{{array_xiaoshuwei}}" value="{{index_xiaoshuwei}}" mode='selector' bindchange='xiaoshuweichanged'> <view class='peizhi-item' wx:if="{{!switchState}}"> <view class='peizhi-item-left'> <view>保留小数位数</view> <view>运算结果经四舍五入后,保留的小数位数</view> </view> <view class='peizhi-item-middle'> <view class='picker'>{{array_xiaoshuwei[index_xiaoshuwei]}}个</view> </view> <view class='peizhi-item-right'><image style='width: 50rpx;height: 50rpx' src='/images/more.png'></image></view> </view> </picker> <view class='line' wx:if="{{!switchState}}"></view> </view> <view><button bindtap='btStart'>开始运算</button></view> <view><button bindtap='btExport' style='margin-top: 20rpx;'>导出题目</button></view> <view class='yunsuanfu' wx:if="{{show_yunsuanfu}}"> <view> <view class='cancel' bindtap='cancelTap'>取消</view> <view class='confirm' bindtap='confirmTap'>确定</view> </view> <checkbox-group bindchange="yunsuanfu_changed"> <view wx:for="{{array_yunsuanfu}}"> <checkbox value='{{item.name}}' checked='{{item.checked}}'>{{item.name}}</checkbox> </view> </checkbox-group> </view> </view> </view> <view wx:else></view>
<!--pages/endless/endless.wxml--> <view class='title'>欢迎进入无尽模式,请选择难度</view> <view> <button data-grade='easy' bindtap='select'>简单</button> <button data-grade='normal' bindtap='select'>一般</button> <button data-grade='hard' bindtap='select'>困难</button> </view>
/* pages/endless/endless.wxss */ button { margin-top: 20rpx; } .title { margin-top: 30rpx; font-size: 36rpx; text-align: center; color: gray; }
3、功能点3_无尽模式得分设计
【简单】两个一位运算数,只有加减法,每对一题累计1分
【一般】三个一位运算数,加减乘,每对一题累计2分
【困难】三个两位运算数,加减乘,每对一题累计3分
三种模式都是:错一题即停止答题,并跳出结果页面。
可以直接分享,邀请群友一起参与,或在“我的”里分享到指定群里查看群排名。
1)做题页面,显示做题时间和已得分数:
2)结果页面:(包含分享和结果复制)
简单模式:每对一题得1分
一般模式:每对一题计2分
困难模式:每对一题计3分
// pages/endless/endless.js Page({ /** * 页面的初始数据 */ data: { }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { }, select: function(e) { var grade=e.target.dataset.grade; if(grade==='easy'){ //简单模式 wx.navigateTo({ url: '/pages/main_endless/main_endless?yunsuanshu=' + 2 + "&weishu=" + 1 + "&yunsuanfu=" + '+,-' + "&timushu=" + 10000 + "&xiaoshuwei=" + 0+"&mode=endless"+"&timushu=-1"+"&time=-1"+"&grade="+grade }); } else if (grade === 'normal'){ //一般模式 wx.navigateTo({ url: '/pages/main_endless/main_endless?yunsuanshu=' + 3 + "&weishu=" + 1 + "&yunsuanfu=" + '+,-,×' + "&timushu=" + 10000 + "&xiaoshuwei=" + 0 + "&mode=endless" + "&timushu=-1" + "&time=-1" + "&grade=" + grade }); } else if (grade === 'hard') { //困难模式 wx.navigateTo({ url: '/pages/main_endless/main_endless?yunsuanshu=' + 3 + "&weishu=" + 2 + "&yunsuanfu=" + '+,-,×' + "&timushu=" + 10000 + "&xiaoshuwei=" + 0 + "&mode=endless" + "&timushu=-1" + "&time=-1" + "&grade=" + grade }); } else { console.log("未知难度"); } } })
// pages/result/result.js var util = require('../../utils/util.js'); Page({ /** * 页面的初始数据 */ data: { result:[],//将以上结果合并后的结果 haoshi:"",//耗费的时间 num:0,//题目数量 correct_num:0,//回答正确的数量 comment:"",//对于这个正确率,给一个评语鼓励之类的话,或者是名人名言,对于儿童,一个漫画是更好的选择 headImage:"",//用户头像的url mode:'',//用户选择的模式 score: 0,//历史最高得分 time: 0,//在exam模式中,规定的考试时间 timushu:0, //在exam模式中规定的题目数量 xishu: 0,//根据难度等级,得到得分系数 }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { //根据难度等级,计算得分系数 if(options.grade==='easy'){ this.setData({xishu:1,mode:'简单'}); } else if (options.grade === 'normal'){ this.setData({ xishu: 2 ,mode:'一般'}); }else{ this.setData({ xishu: 3 ,mode:'困难'}); } var that=this; var app = getApp(); this.setData({ headImage: app.globalData.userInfo.avatarUrl }); //获取用户操作的信息 console.log(options); var begin_time=0; var end_time=Number(options.haoshi)*1000; this.setData({haoshi:util.calcHaoshi(begin_time,end_time)});//将耗时进行格式化,用于显示 var timu=options.timu.split(","); var answer=options.answer.split(","); var correct_result=options.correct_result.split(","); var result=[]; var obj={}; for(var i=0;i<timu.length;i++){ obj={timu:timu[i],answer:answer[i],correct_result:correct_result[i]}; result.push(obj); } this.setData({result:result}); this.setData({correct_num: this.calcCorrectNum(result)}); this.setData({num:result.length}); this.setData({time: options.time,timushu: options.timushu}); var score = wx.getStorageSync('score') || 0; if(score<this.data.correct_num){ wx.setStorageSync('score', this.data.correct_num); this.setData({ score: this.data.correct_num}); }else { this.setData({ score: score }); } //将本次得分上传服务器 let cs={}; cs['openID']=getApp().globalData.secret.openid; cs['score']=this.data.correct_num; wx.request({ url: getApp().globalData.host + "InsertScoreszys", data: cs, method: 'POST', header: { 'content-type': "application/x-www-form-urlencoded" }, success: function (e) { console.log(e.data); } });//end of 本次得分上传服务器 }, calcCorrectNum: function(result){ var correct_num=0; for(var i=0;i<result.length;i++){ if(result[i].answer===result[i].correct_result){ correct_num++; } } return correct_num; }, copytap: function(){ //导出结果 var res=this.data.result; //obj={timu:timu[i],answer:answer[i],correct_result:correct_result[i]}; var result="题目\t答案\t正确结果\t结论"; var t=""; for(var i=0;i<res.length;i++){ if(res[i].answer===res[i].correct_result){ t="正确"; }else{ t="错误"; } result=result+"\n"+res[i].timu+"="+"\t"+res[i].answer+"\t"+res[i].correct_result+"\t"+t; } wx.setClipboardData({ data: result, success: function(res){ wx.showModal({ title: '复制成功', content: '已复制,请打开记事本或Excel完成粘贴', showCancel: false, confirmText: '知道了', confirmColor: '#349023' }); }, fail: function(res){ console.log(res); wx.showModal({ title: '复制失败', content: '没有完成复制', showCancel: false, confirmText: '知道了', confirmColor: '#349023' }); } }); }, sharetap: function(){ }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { var score = wx.getStorageSync('score') || 0; return{ title: '我在无尽模式中的最高得分是'+score+'分,敢来挑战吗?', path : '/pages/index/index' } } })
两种分享结果的方法:
群内分享页面:
// pages/contact/contact.js Page({ /** * 页面的初始数据 */ data: { imgUrl: '' }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { var url=getApp().globalData.host+'GetHeadImage?username=ad1'; this.setData({imgUrl: url}); }, scan: function(e) { console.log(e); wx.scanCode({ success: function(e) { console.log(e); } }) }, copy: function(e) { wx.setClipboardData({ data: 'pangxiebiancheng', success: function(e) { wx.showToast({ title: '复制成功,请用微信搜索联系人', }); }, fail: function(e) { wx.showToast({ title: '复制失败', }); } }) }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
4、功能点4_ 无尽模式参与微信好友排名
群排名的原理是,通过分享一个固定的页面到群里,当群成员点击分享链接进入到小程序后,小程序会获得这个群的id,将群id与用户id保存在服务器的数据库中,服务器会返回该群的成员的排名。
// pages/paiming/paiming.js var util = require('../../utils/util.js'); Page({ /** * 页面的初始数据 */ data: { userlist:[],//返回排名信息的列表 isShow: true,//是否显示提示信息和按钮 }, /** * 生命周期函数--监听页面加载 */ onShow: function (options) { var that=this; console.log("paiming.wxml"); setTimeout(function(){ //将openGId与openid的关系对上传到服务器 let cs = {}; cs['openID'] = getApp().globalData.secret.openid; cs['openGId'] = getApp().globalData.openGId; wx.request({ url: getApp().globalData.host + "InsertRelationshipBetweenOpenIdAndOpenGIdszys", data: cs, method: 'POST', header: { 'content-type': "application/x-www-form-urlencoded" }, success: function (e) { console.log(e.data); //获取群排名 let cs = { 'openGId': getApp().globalData.openGId }; wx.request({ url: getApp().globalData.host + "GetQunPaiMing", data: cs, method: 'POST', header: { 'content-type': "application/x-www-form-urlencoded" }, success: function (e) { console.log(e.data); that.setData({ userlist:e.data,isShow:!that.data.isShow}); } });//end of 获取群排名 } });//end of openGId与openid的关系对上传到服务器 },6000); }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { }, openMini: function(){ wx.switchTab({ url: '/pages/index/index', }); } })
点击上图下拉菜单,新建编译模式,上述填写的内容,配置好模式。选择该模式后会出现下面的界面,测试成功:
// pages/allpaiming/allpaiming.js Page({ /** * 页面的初始数据 */ data: { users:[],//用户信息 }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { var that=this; //获取全部用户的信息 wx.request({ url: getApp().globalData.host + "GetAllPaiMing", method: 'POST', header: { 'content-type': "application/x-www-form-urlencoded" }, success: function (e) { that.setData({users:e.data}); } });//end of 获取全部用户信息 }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
页面式样实现:
/* pages/allpaiming/allpaiming.wxss */ image { width: 80rpx; height: 80rpx; vertical-align: middle; } .lb { display: flex; justify-content: space-around; align-items: center; margin-top: 20rpx; }
系统功能测试代码:
{ "description": "项目配置文件。", "setting": { "urlCheck": false, "es6": true, "postcss": true, "minified": true, "newFeature": true }, "compileType": "miniprogram", "libVersion": "1.6.4", "appid": "wx281e819778f97d50", "projectname": "%E5%9B%9B%E5%88%99%E8%BF%90%E7%AE%97%E5%87%BA%E9%A2%98%E5%99%A8%E5%A8%B1%E4%B9%90%E6%A8%A1%E5%BC%8F", "condition": { "search": { "current": -1, "list": [] }, "conversation": { "current": -1, "list": [] }, "game": { "current": -1, "list": [] }, "miniprogram": { "current": -1, "list": [ { "id": 0, "name": "群排名", "pathName": "pages/paiming/paiming", "query": "", "scene": "1044", "shareInfo": { "groupName": "测试模拟群0", "shareName": "8IdvumOXlRxM7zcX3Iuyhg4OXuhflgaazFh6-SbJ9rk@cr4dev", "shareKey": "MUdcFDmsac0d8E4dUVOX6O9ps6yh9OYMazT7WQfTa6Ny9H1V92eO0W6HhcG1hv290XEM8BTe5xf4nW99LKPDcA~~" } } ] } } }
5、功能点5_考试模式用户自由选择运算符(类型和个数)以及出题数目
功能点6_考试模式可以设置时间限制
考试模式界面:
用户自由设置题目数量、运算符形式和考试时间;如果超时,将停止答题。
考试模式_出题过程:
记录用户输入的答案,并且判断是否改变按钮文字;
判断是否题目回答完了,要跳转出最后结果页面。
//index.js //获取应用实例 const app = getApp() var util=require('../../utils/util.js'); Page({ data: { motto: 'Hello World', userInfo: {}, hasUserInfo: false, canIUse: wx.canIUse('button.open-type.getUserInfo'), array_yunsuanshu: [2,3,4,5],//参与运算的数字个数 index_yunsuanshu: 0, array_weishu: [1,2,3,4],//参与运算的数字位数 index_weishu: 0, array_timushu: [10,15,20,25,30],//出题数量 index_timushu: 2, array_xiaoshuwei: [1,2,3,4],//结果保留的小数位 index_xiaoshuwei: 0, xiaoshuwei_result: 0, array_yunsuanfu: [{ name: '+', checked: false }, { name: '-', checked: false }, { name: '×', checked: false }, { name: '÷', checked: false }], array_yunsuanfu_result: ['+','-'],//默认选中的运算符是+和- array_yunsuanfu_tmp: [],//运算符的临时数组,用来记录checkbox-group变化时的返回值 show_yunsuanfu: false,//运算符配置的页面默认是false,不显2 switchState: true, timu:[],//题目数组 correct_result:[],//答案数组 array_time: [5,10,15,20,25],//待选择的考试时间,单位是分钟 index_time: 0,//默认的array_time下标 time_result: 300,//答题时间,用户选择时是分钟,传递参数时最终转换为秒 isAuthority: getApp().globalData.authority, }, switchChange: function(e){//结果取整 this.setData({switchState: e.detail.value}); if(e.detail.value==true){ this.setData({xiaoshuwei_result:0}); }else{ this.setData({xiaoshuwei_result:this.data.array_xiaoshuwei[this.data.index_xiaoshuwei]}); } }, timechanged: function(e) { //设置考试时间 this.setData({index_time: e.detail.value}); var time=this.data.array_time[this.data.index_time]*60; this.setData({time_result:time}); }, //事件处理函数 btStart: function(e) { wx.navigateTo({ url: '/pages/main_exam/main_exam?yunsuanshu=' + this.data.array_yunsuanshu[this.data.index_yunsuanshu] + "&weishu=" + this.data.array_weishu[this.data.index_weishu] + "&yunsuanfu=" + this.data.array_yunsuanfu_result + "&timushu=" + this.data.array_timushu[this.data.index_timushu] + "&xiaoshuwei=" + this.data.xiaoshuwei_result + "&mode=exam" + "&time=" + this.data.time_result }); }, btExport: function(e){ var num = this.data.array_timushu[this.data.index_timushu]; for(var i=0;i<num;i++){ var result = util.getBiaodashi(this.data.array_yunsuanshu[this.data.index_yunsuanshu], this.data.array_yunsuanfu_result, this.data.array_weishu[this.data.index_weishu], this.data.timu, this.data.correct_result, this.data.xiaoshuwei_result);//return {correct_result: correct_result,biaodashi: biaodashi, timu: timu} //yunsuanshu, yunsuanfu, weishu, timu, correct_result, xiaoshuwei this.setData({ correct_result: result.correct_result, timu: result.timu }); } var timu=this.data.timu; var correct_result=this.data.correct_result; var text="题目\t正确答案"; for(var i=0;i<timu.length;i++){ text=text+"\n"+timu[i]+"\t"+correct_result[i]; } wx.setClipboardData({ data: text, success: function(){ wx.showModal({ title: '成功', content: '请打开Excel或记事本,完成粘贴', }); } }) }, yunsuanfu_change: function(e){//点击了运算符配置选项后,配置页面出现了 this.setData({show_yunsuanfu: true}); }, timushuchanged: function(e){ this.setData({index_timushu:e.detail.value}); }, yunsuanfu_changed: function(e){// this.setData({array_yunsuanfu_tmp:e.detail.value}); }, onLoad: function () { console.log("获取的数据:"+this.data.isAuthority); this.setData({isAuthority: getApp().globalData.authority}); wx.setNavigationBarTitle({ title: '四则运算练习器', }); if (app.globalData.userInfo) { this.setData({ userInfo: app.globalData.userInfo, hasUserInfo: true }) } else if (this.data.canIUse){ // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 // 所以此处加入 callback 以防止这种情况 app.userInfoReadyCallback = res => { this.setData({ userInfo: res.userInfo, hasUserInfo: true }) } } else { // 在没有 open-type=getUserInfo 版本的兼容处理 wx.getUserInfo({ success: res => { app.globalData.userInfo = res.userInfo this.setData({ userInfo: res.userInfo, hasUserInfo: true }) } }) } }, getUserInfo: function(e) { console.log(e) app.globalData.userInfo = e.detail.userInfo this.setData({ userInfo: e.detail.userInfo, hasUserInfo: true }) }, confirmTap: function(e){ //选中的运算符不能为空 if (this.data.array_yunsuanfu_tmp.length == 0) { wx.showToast({ title: '必须至少选择一个运算符', duration: 2000 }); return; } this.setData({array_yunsuanfu_result:this.data.array_yunsuanfu_tmp}); this.setData({show_yunsuanfu:false}); }, cancelTap: function(e){ this.setData({ array_yunsuanfu_result:['+','-']}); this.setData({show_yunsuanfu:false}); }, yunsuanshuchanged: function(e) { this.setData({index_yunsuanshu:e.detail.value}); }, weishuchanged: function(e) { this.setData({index_weishu:e.detail.value}); }, xiaoshuweichanged: function(e) { this.setData({index_xiaoshuwei:e.detail.value}); this.setData({ xiaoshuwei_result: this.data.array_xiaoshuwei[this.data.index_xiaoshuwei] }); }, onShareAppMessage: function () { return { title: '四则运算自动出题器', path: '/pages/index/index' } } })
var util = require('../../utils/util.js'); var id = 0; var num = 0;//剩余题目数量 Page({ data: { yunsuanshu: 0,//参与运算的数字个数 weishu: 0,//参与运算的数字位数 yunsuanfu: [],//参与运算的运算符,这是个数组 originalTimushu: 0,//原始的题目数 timushu: 1,//题目数量 xioashuwei: 0,//结果的保留的位数 bt_text: "下一题", biaodashi: "", timu: [],//题干 answer: [],//用户输入结果 correct_result: [],//正确结果 input: "",//用户输入的答案 focus: true,//输入框获得焦点 begin_time: 0,//记录开始时间 time: 0,//初始的时间,即选定的考试时间 daojishi: 0,//倒计时,即在页面上展示的时间 isAuthority: getApp().globalData.authority }, answer_input: function (e) { //用户输入时,记录输入内容 var input = e.detail.value; this.setData({ input: input }); }, onUnload: function(e){ clearInterval(id); }, onLoad: function (option) { this.setData({ yunsuanshu: option.yunsuanshu, weishu: option.weishu, yunsuanfu: option.yunsuanfu.split(","), timushu: option.timushu, originalTimushu: option.timushu, xiaoshuwei: option.xiaoshuwei, begin_time: new Date().getTime(), time: Number(option.time)//设置的考试时长 }); num = option.timushu;//剩余题目数量 var that = this; var t = Number(option.time);//设置的考试时长 console.log(option); id = setInterval(function () { t--; that.setData({ daojishi: t }); if (t <= 0) { //应该停止运算了,时间到了 clearInterval(id); wx.showModal({ title: '时间到了', content: '时间到,点击确定查看结果', showCancel: false, success: function (res) { if (res.confirm) { var tm = util.simplyBiaodashi(that.data.timu);//将this.data.timu中的等号取消掉,否则传参数的时候,会有bug wx.redirectTo({ url: '/pages/result_exam/result_exam?timu=' + tm + "&answer=" + that.data.answer + "&correct_result=" + that.data.correct_result + "&time=" + that.data.time + "&timushu=" + that.data.originalTimushu + "&haoshi=" + that.data.time * 1000//haoshi是实际用时,因为时间到,因此实际用时与考试的规定时间是一致的。 }); } } }); } }, 1000); //初始化题目 var result = util.getBiaodashi(this.data.yunsuanshu, this.data.yunsuanfu, this.data.weishu, this.data.timu, this.data.correct_result, this.data.xiaoshuwei);//return {correct_result: correct_result,biaodashi: biaodashi, timu: timu} this.setData({ correct_result: result.correct_result, biaodashi: result.biaodashi, timu: result.timu }); num--;//第一道题出完,因此num-- }, nextTap: function () { //下一题 /* 剩余题目数-1; 记录用户输入的答案; 判断是否应该更改按钮的提示文字了 判断是否该跳转到结果报告了 */ var ans = this.data.answer; ans.push(this.data.input); this.setData({ answer: ans }); if (num == 0) { clearInterval(id); //页面跳转查看结果 var haoshi = new Date().getTime() - this.data.begin_time; var tm = util.simplyBiaodashi(this.data.timu);//将this.data.timu中的等号取消掉,否则传参数的时候,会有bug var url = '/pages/result_exam/result_exam?timu=' + tm + "&answer=" + this.data.answer + "&correct_result=" + this.data.correct_result + "&time=" + this.data.time + "&timushu=" + this.data.originalTimushu + "&haoshi=" + haoshi; console.log(url); wx.redirectTo({ url: url }); } else { this.setData({ input: '', focus: true });//清空,还原 this.setData({ timushu: num }); var result = util.getBiaodashi(this.data.yunsuanshu, this.data.yunsuanfu, this.data.weishu, this.data.timu, this.data.correct_result, this.data.xiaoshuwei);//return {correct_result: correct_result,biaodashi: biaodashi, timu: timu} this.setData({ correct_result: result.correct_result, biaodashi: result.biaodashi, timu: result.timu }); num--;//出完一道题,num-- } if (num == 0) { //最后一题 this.setData({ bt_text: "完成,查看结果" }); } } } )
<view class="page" wx:if="{{isAuthority=='是'}}" xmlns:wx="http://www.w3.org/1999/xhtml"> <view>倒计时:{{daojishi}}秒,剩余题目:{{timushu}}道</view> <view class='question'>{{biaodashi}}</view> <input class='answer' type='text' focus='{{focus}}' type='digit' confirm-type='确定' bindinput='answer_input' value='{{input}}'></input> <button id='bt' bindtap="nextTap">{{bt_text}}</button> </view> <view wx:else>请联系微信号:pangxiebiancheng获取授权</view>
6、功能点7_ 考试模式每套考试题结束后出现统计页面,显示正确率
// pages/result/result.js var util=require('../../utils/util.js'); Page({ /** * 页面的初始数据 */ data: { result:[],//将以上结果合并后的结果 haoshi:"",//耗费的时间 num:0,//题目数量 correct_num:0,//回答正确的数量 comment:"",//对于这个正确率,给一个评语鼓励之类的话,或者是名人名言,对于儿童,一个漫画是更好的选择 headImage:"",//用户头像的url mode:'',//用户选择的模式 score: 0,//历史最高得分 time: 0,//在exam模式中,规定的考试时间 timushu:0 //在exam模式中规定的题目数量 }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { var that=this; var app=getApp(); console.log(app.globalData); this.setData({headImage: app.globalData.userInfo.avatarUrl}); //获取用户操作的信息 console.log(options); var end_time=options.haoshi; this.setData({haoshi:util.calcHaoshi(0,end_time)});//将耗时进行格式化,用于显示 var timu=options.timu.split(","); var answer=options.answer.split(","); var correct_result=options.correct_result.split(","); var result=[]; var obj={}; for(var i=0;i<timu.length;i++){ obj={timu:timu[i],answer:answer[i],correct_result:correct_result[i]}; result.push(obj); } this.setData({result:result}); this.setData({correct_num: this.calcCorrectNum(result)}); this.setData({num:result.length}); this.setData({time: util.calcHaoshi(0,Number(options.time)*1000),timushu: options.timushu}); var score = wx.getStorageSync('score') || 0; if(score<this.data.correct_num){ wx.setStorageSync('score', this.data.correct_num); this.setData({ score: this.data.correct_num}); }else { this.setData({ score: score }); } }, calcCorrectNum: function(result){ var correct_num=0; for(var i=0;i<result.length;i++){ if(result[i].answer===result[i].correct_result){ correct_num++; } } return correct_num; }, copytap: function(){ //导出结果 var res=this.data.result; //obj={timu:timu[i],answer:answer[i],correct_result:correct_result[i]}; var result="题目\t答案\t正确结果\t结论"; var t=""; for(var i=0;i<res.length;i++){ if(res[i].answer===res[i].correct_result){ t="正确"; }else{ t="错误"; } result=result+"\n"+res[i].timu+"="+"\t"+res[i].answer+"\t"+res[i].correct_result+"\t"+t; } wx.setClipboardData({ data: result, success: function(res){ wx.showModal({ title: '复制成功', content: '已复制,请打开记事本或Excel完成粘贴', showCancel: false, confirmText: '知道了', confirmColor: '#349023' }); }, fail: function(res){ console.log(res); wx.showModal({ title: '复制失败', content: '没有完成复制', showCancel: false, confirmText: '知道了', confirmColor: '#349023' }); } }); }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { var mode=this.data.mode; var score = wx.getStorageSync('score') || 0; return { title :'我的本次考试中的'+this.data.num+'题中做对了'+this.data.correct_num+'题', path: '/pages/index/index' } } })
7、 功能点8_可以打印一套试卷,导出(复制黏贴)到微信小程序之外
设置好后点击“导出题目”,则跳出窗口如图:
剪贴板黏贴效果:
8、功能点9_ 可以从程序内一键分享到微信聊天框;
分享内容为 考试模式的正确率 或 无尽模式的得分
<!--pages/paiming/paiming.wxml--> <text wx:if="{{isShow}}">正在获取排名,请稍候……</text> <view class='lb'> <text>排名</text> <view>用户</view> <text>得分</text> </view> <block wx:for="{{userlist}}"> <view class='lb'> <text>{{index+1}}</text> <view> <image src='{{item.avatarUrl}}' mode='aspectFit'></image> <text>{{item.nickName}}</text> </view> <text>{{item.score}}</text> </view> </block> <button bindtap='openMini' wx:if="{{!isShow}}">打开小程序</button>
9、功能点10_无尽模式一周清空排名,重新记录,同时保留历史最高成绩
1)保留历史最高记录:将最高成绩保存在小程序里。
2)一周清空排名:利用数据库,在服务器上实现该功能。
第一次执行的时候,设定周一零点的时候,执行该命令,将用户的成绩全部清零:
CREATE EVENT update_score ON SCHEDULE EVERY 7 DAY DO update szys_userinfo set score=0;
在MySQL数据库命令行中,执行上述语句,则每七天清空一次,重新记录。
解决项目的心路历程与收获:
这个项目对我们来说难度很大,最开始由于时间紧张和对知识的不了解,错误地安排了项目完成计划。在老师的理解和帮助下我们继续开展学习和开发,期间得到了朋友的帮助和指导,基本按照第二次安排时间完成了项目预计进度。在软件工程团队项目中,有很多不可控因素,但是调动大家积极性并且合理分工还是非常重要的。小程序的代码很是庞杂,技术上解决的问题不再赘述,这次的大作业是一次非常宝贵的编程经验,学习了解了更多的语言,发现了更多的学习渠道。