接着上篇博文已经完成签到功能,这篇来完成课堂测试功能。
一、需求描述
1、在后台选择题、主观题表中上传测试题
2、客户端获取题目信息
3、把题目信息格式化加载显示
4、客户端答题,主观题每题能上传一张答题图片
5、客户端答题结束提交到服务器
二、前台页面
提交大量数据当然用表单了,代码并不多。wxml代码如下:
<view class='section' hidden='{{selectedScrollItemsHiddenSign[1]}}'>
<form bindsubmit="formSubmit">
<view class='section item-block' style='text-align:
center;font-size=20px;'>{{onlineTestSeries}}</view>
<view class='section item-block'>选择题</view>
<view class='section item-block' wx:for="{{onlineTestChooseItems}}"
wx:for-item="i" wx:key="*unique">
<view class='choose-item-title'>{{i.number_id}}、{{i.title}}</view>
<radio-group class="radio-group" name="choose-radio-group{{i.number_id}}">
<label class="radio" wx:for="{{i.chooseItems}}" wx:key="*unique">
<radio value="{{item.name}}"/>
{{item.value}}
</label>
</radio-group>
</view>
<view class='section item-block'>简答题</view>
<view class='section item-block' wx:for="{{onlineTestSelfQuestionItems}}"
wx:key="*unique">
<view class='choose-item-title'>{{item.number_id}}、{{item.title}}</view>
<textarea class="answer-mainbody" name="answer-textarea{{item.number_id}}"
placeholder="在这里写下您的答案..." type="textarea"/>
<view class="answer-img" name="answer-img{{item.number_id}}" id="{{index}}"
bindtap='chooseAnswerImage'></view>
<text class='answer-img-text'>{{answerImgPaths[index].path}}</text>
</view>
<view class='section item-block'>
<button formType="submit" class='btn-commit'>提交</button>
</view>
</form>
</view>
wcss代码如下:
/* 每个题目块样式 */
.item-block{
margin: 2%;
background: rgb(232, 233, 232);
color: royalblue;
font-size: 15px;
padding: 4%;
border-radius: 3%;
width: 96%;
display: flex;
flex-direction: column;
}
/* 单选题radio-group样式 */
.radio-group{
margin: 2%;
display: flex;
flex-direction: column;
}
/* 主观题答题区 */
.answer-mainbody{
height: 150px;
margin: 2%;
padding: 6px;
font-family: monospace;
white-space: pre-wrap;
background: whitesmoke;
border-radius: 3%;
border: 1px solid rgb(214, 214, 214);
}
/* 主观题图片上传 */
.answer-img{
margin: 1%;
background:
url(http://i.pengxun.cn//content/images/imgpost2/01/post-big-pic-01.png)
no-repeat 0 -286px;
background-size: 100%;
height: 26px;
width: 26px;
}
.answer-img-text{
height: 25px;
padding: 6px;
/* 超出一行文字自动隐藏 */
overflow:hidden;
/* 文字隐藏后添加省略号 */
text-overflow:ellipsis;
/* 强制不换行 */
white-space:nowrap;
}
.btn-commit{
background: royalblue;
color: whitesmoke;
}
三、后台代码
1、初始data
data: {
// 选择题题目信息列表
onlineTestChooseItems:null,
//课堂测试试题系列
onlineTestSeries:null,
// 主观题题目信息列表
onlineTestSelfQuestionItems:null,
// 模拟测试试题系列
simulateTestSeries: null,
// 主观题答题图片路径列表
answerImgPaths:[],
},
2、获取题目信息
/**
* 获得课堂测试答题信息
* id为scroll-view menu中已经加载完成的index
* testType为测试类型,可以是课堂测试、模拟测试或者其他,以服务器中的数据为参照
*/
getPracticeItemsInfo:function(id,testType){
let query_choose = Bmob.Query('choose_item');
query_choose.order('number_id');
query_choose.equalTo("type", "==", testType);
query_choose.find().then(res => {
if(res.length>0){
if (testType=='课堂测试') that.setData({ onlineTestSeries:res[0].series});
else that.setData({ simulateTestSeries: res[0].series });
}
var choose_items = new Array();
for (var i = 0; i < res.length; i++) {
choose_items[i] = {
objectId: res[i].objectId,
number_id: res[i].number_id,
title: res[i].title,
chooseItems: [
{ name: 'a', value: res[i].choose_item_a },
{ name: 'b', value: res[i].choose_item_b },
{ name: 'c', value: res[i].choose_item_c },
{ name: 'd', value: res[i].choose_item_d }
]
}
}
let query_subjective = Bmob.Query('subjective_item');
query_subjective.order('number_id');
query_choose.equalTo("type", "==", testType);
query_subjective.find().then(res_subjective => {
var subjective_items = new Array();
for (var i = 0; i < res_subjective.length; i++) {
subjective_items[i] = {
objectId: res_subjective[i].objectId,
number_id: res_subjective[i].number_id,
title: res_subjective[i].title
};
}
that.data.selectedScrollItemsLoadingComplete[id] = true;
that.setData({ onlineTestChooseItems: choose_items,
onlineTestSelfQuestionItems: subjective_items,
selectedScrollItemsLoadingComplete:
that.data.selectedScrollItemsLoadingComplete });
wx.hideToast();
}).catch(err => {
wx.hideToast();
console.log(err.msg);
wx.showToast({
title: '加载出错',
duration: 2000
});
});
}).catch(err => {
wx.hideToast();
console.log(err);
wx.showToast({
title: '加载出错',
duration: 2000
});
});
},
3、上传主观题图片
先上传主观题图片,然后把上传好的url记录下来,最后保存到对应的主观题目即可。查看文档发现上传图片要用wx.api中的wx.chooseImage(Object
object),object的参数如下表:
属性 类型 默认值 是否必填 说明
count number 9 否 最多可以选择的图片张数
sizeType Array.<string> ['original', 'compressed'] 否 所选的图片的尺寸
sourceType Array.<string> ['album', 'camera'] 否 选择图片的来源
success function 否 接口调用成功的回调函数
fail function 否 接口调用失败的回调函数
complete function 否 接口调用结束的回调函数(调用成功、失败都会执行)
于是得到如下上传代码:
// 主观题选择答题拍照或者图片
chooseAnswerImage: function(e) {
wx.chooseImage({
count: 1, // 默认9
sizeType: ['original'], // 指定是原图
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function(res) {
// 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
// 如果该题已经上传答题图片则先删除原先的图片
var currentAnswerImgPathsItem = that.data.answerImgPaths[e.currentTarget.id];
if (currentAnswerImgPathsItem != null) {
// 传入string是单个文件删除,传入array是批量删除
var del = Bmob.File();
del.destroy([currentAnswerImgPathsItem.file.url]).then(res1 => {
that.uploadAnswerImg(e.currentTarget.id, res.tempFilePaths[0]);
}).catch(err => {
console.log(err);
wx.showToast({
title: '图片上传失败',
duration: 2000
})
})
} else {
that.uploadAnswerImg(e.currentTarget.id, res.tempFilePaths[0]);
}
}
});
},
uploadAnswerImg: function(pathId, pathImg) {
// 上传选中的图片
var file = Bmob.File('subjectImg.jpg', pathImg);
wx.showToast({
title: '正在上传',
icon: 'loading',
duration: 10000
});
file.save().then(res => {
wx.hideToast();
wx.showToast({
title: '图片上传成功',
duration: 2000
})
that.data.answerImgPaths[pathId] = {
path: pathImg,
file: res[0]
};
that.setData({
answerImgPaths: that.data.answerImgPaths
});
}).catch(err => {
console.log(err);
wx.showToast({
title: '图片上传失败',
duration: 2000
})
});
},
4、提交答题数据
提交答案分选择题和主观题,主观题还有对应的图片,由于bmob中pointer类型不能直接新增,因此在新增一条含有pointer字段的数据时,要先增其他的信息成功后在回调函数中再把对应的pointer类型数据插入。下面是上传一个题目的代码:
/**
* 上传单个题信息
* value是题目信息
* itemType是题目类型
*/
uploadAItemAnswersInfo: function(value, itemType) {
var query = Bmob.Query('choose_item_submit');
query.set("answer", value.answer);
if (value.file != null)
query.set("subjectiveImg", value.file);
// 先保存answer和上传成功的图片信息
query.save().then(res => {
query.get(res.objectId).then(res1 => {
// 保存关联对象
// 设置用户关联对象
var userIdPointer = Bmob.Pointer('_User');
var pointerUserId = userIdPointer.set(value.userId);
res1.set('userId', pointerUserId);
if (itemType == 'choose') {
// 设置选择题关联对象
var chooseItemIdPointer = Bmob.Pointer('choose_item'); //关联字段
var pointerIdChooseItemId = chooseItemIdPointer.set(value.chooseItemId);
res1.set('chooseItemId', pointerIdChooseItemId);
// 设置后保存
res1.save();
} else {
// 设置主观题关联对象
var subjectItemIdPointer = Bmob.Pointer('subjective_item'); //关联字段
var pointerIdSubjectItemId = subjectItemIdPointer.set(value.subjectItemId);
res1.set('subjectiveItemId', pointerIdSubjectItemId);
res1.save();
}
return true;
});
}).catch(err => {
wx.showToast({
title: '上传失败',
duration: 2000
});
return false;
});
},
接下来是只要把表单中的所有答题信息遍历,按类型上传即可,不过再上传结果判定时要确保每个题目都已经上传,有一个题目上传不成功就提示没有提交成功。下面是上传所有答题数据的代码:
/**
* 上传所有答案信息
* value是表单提交上来的信息
*/
uploadAnswersInfo: function(value) {
// 显示加载loading
wx.showToast({
title: '提交中...',
icon: 'loading',
duration: 1000
});
// 上传题目结果列表
var allUploadSuccess = [];
// 选择题索引
var chooseItemIndex = 0;
// 主观题索引
var subjectiveItemIndex = 0;
// 遍历submit提交的数据
for (var keyname in value)
if (value[keyname] != "") {
//console.log(value.keyname);
// 判断是否是单选题
var data = {};
if (keyname.indexOf('choose-radio-group') != -1) {
data = {
userId: app.globalData.currentUser.objectId,
chooseItemId: that.data.onlineTestChooseItems[chooseItemIndex].objectId,
file: null,
answer: value[keyname]
}
allUploadSuccess[chooseItemIndex + subjectiveItemIndex] =
that.uploadAItemAnswersInfo(data, 'choose');
chooseItemIndex++;
} else {
var fileName = null;
if (that.data.answerImgPaths[subjectiveItemIndex] != null) fileName =
that.data.answerImgPaths[subjectiveItemIndex].file;
data = {
userId: app.globalData.currentUser.objectId,
subjectItemId:
that.data.onlineTestSelfQuestionItems[subjectiveItemIndex].objectId,
file: fileName,
answer: value[keyname]
}
allUploadSuccess[chooseItemIndex + subjectiveItemIndex] =
that.uploadAItemAnswersInfo(data, 'subjective');
subjectiveItemIndex++;
}
}
// 定时监听所有返回结果
var checkResult = setInterval(function() {
if (allUploadSuccess.length == that.data.onlineTestChooseItems.length +
that.data.onlineTestSelfQuestionItems.length) {
var allUploadSuccessFlag = true;
for (var i = 0; i < allUploadSuccess.length; i++) {
if (allUploadSuccess[i] == false) {
allUploadSuccessFlag = false;
if (i > that.data.onlineTestChooseItems.length)
wx.showToast({
title: '第' + (i + 1) + '选择题上传失败',
duration: 1000
});
else {
wx.showToast({
title: '第' + (i + 1 - that.data.onlineTestChooseItems.length) + '主观题上传失败',
duration: 1000
});
}
}
}
// 清除定时器
clearInterval(checkResult);
wx.hideToast();
// 所有题目都成功提交
if (allUploadSuccessFlag) {
// 上传提交记录
var query = Bmob.Query('submit_record');
query.set("type", '课堂测试');
query.set('series', that.data.onlineTestSeries);
query.save().then(res => {
query.get(res.objectId).then(res1 => {
var pointer = Bmob.Pointer('_User');
var poiID = pointer.set(app.globalData.currentUser.objectId);
res1.set('userId', poiID);
res1.save();
// 显示加载logo
wx.showToast({
title: '提交成功',
duration: 3000
});
}).catch(err => {});
}).catch(err => {});
}
} else {
wx.showToast({
title: '提交中...',
icon: 'loading',
duration: 1000
});
}
}, 1000);
},
四、效果
1、服务器题目信息
选择题信息
主观题信息
2、获取题目信息加载
3、提交结果
然后课堂测试和模拟测试功能一样,就是换个数据,因为课堂提问要实时推送,而bmob实时推送是收费的,这在后面做最后的完成,下节先实现练习模块的前台。
热门工具 换一换