序言
一个在线教育平台,习题上传功能是不可缺少的。这里基于Nodejs的Express框架,接入MongoDB数据库,实现习题的创建,作答和统计,并使用Echart展示数据。
前期设计
流程设计
一个简单的习题功能,包括了教师的习题创建、学生的习题作答和教师的习题统计数据查看的三部分功能。
页面设计
教师课程页面的习题创建和查看入口
教师创建新习题页面,包括选择题和填空题
教师习题数据统计页面,使用柱状图进行数据的展示,并且可以查看具体的学生名单
学生的习题作答界面
前端脚本
习题上传脚本
主要需要动态修改题目数量、填入题目内容与答案、将填入好的习题数据发送至服务端的功能
$(document).ready(() => {
// 添加选择题
$("#addChoice").click(function() { // 点击增加选择题
$("#choice").append('\
// 选择题的具体HTML代码
');
// jQ动态创建的DOM元素,需要通过父元素指定来绑定事件
$("#choice").on("click", ".delete", function() {
$(this).parent().remove();
});
});
});
$(document).ready(() => {
$("#addCompletion").click(function() { // 点击增加填空题
$("#completion").append('\
// 填空题的具体HTML码
')
})
// 同样绑定移除填空题的事件
$("#completion").on("click", ".delete", function() {
$(this).parent().remove();
});
})
$(document).ready(() => {
var choice = [];
var completion = [];
$("#submit").click(() => {
$("#choice >li").each(function() {
// 利用jQ选择题,获得填入的选择题信息
});
$("#completion >li").each(function() {
// 利用jQ选择题,获得填入的填空题信息
});
// 将习题数据Post发出
$.post("process_upload", {test: JSON.stringify(test)}, (data) => {
// ...
}, "json");
})
})
- $(document).ready(() => {
- // 添加选择题
- $("#addChoice").click(function() { // 点击增加选择题
- $("#choice").append('\
- // 选择题的具体HTML代码
- ');
- // jQ动态创建的DOM元素,需要通过父元素指定来绑定事件
- $("#choice").on("click", ".delete", function() {
- $(this).parent().remove();
- });
- });
- });
- $(document).ready(() => {
- $("#addCompletion").click(function() { // 点击增加填空题
- $("#completion").append('\
- // 填空题的具体HTML码
- ')
- })
- // 同样绑定移除填空题的事件
- $("#completion").on("click", ".delete", function() {
- $(this).parent().remove();
- });
- })
- $(document).ready(() => {
- var choice = [];
- var completion = [];
- $("#submit").click(() => {
- $("#choice >li").each(function() {
- // 利用jQ选择题,获得填入的选择题信息
- });
- $("#completion >li").each(function() {
- // 利用jQ选择题,获得填入的填空题信息
- });
-
- // 将习题数据Post发出
- $.post("process_upload", {test: JSON.stringify(test)}, (data) => {
- // ...
- }, "json");
- })
- })
$(document).ready(() => {
// 添加选择题
$("#addChoice").click(function() { // 点击增加选择题
$("#choice").append('\
// 选择题的具体HTML代码
');
// jQ动态创建的DOM元素,需要通过父元素指定来绑定事件
$("#choice").on("click", ".delete", function() {
$(this).parent().remove();
});
});
});
$(document).ready(() => {
$("#addCompletion").click(function() { // 点击增加填空题
$("#completion").append('\
// 填空题的具体HTML码
')
})
// 同样绑定移除填空题的事件
$("#completion").on("click", ".delete", function() {
$(this).parent().remove();
});
})
$(document).ready(() => {
var choice = [];
var completion = [];
$("#submit").click(() => {
$("#choice >li").each(function() {
// 利用jQ选择题,获得填入的选择题信息
});
$("#completion >li").each(function() {
// 利用jQ选择题,获得填入的填空题信息
});
// 将习题数据Post发出
$.post("process_upload", {test: JSON.stringify(test)}, (data) => {
// ...
}, "json");
})
})
习题作答脚本
只需要利用jQ Ajax功能获得题目并展示,以及将作答数据Post回服务端
$(document).ready(() => {
$.get("/tests?id=" + id, (data) => { // 获得JSON格式的试题数据
data.choice.forEach((item, index, array) => {
$("#choice").append('\
// 选择题的具体HTML代码
')
});
data.completion.forEach((item, index, array) => {
$("#completion").append('\
// 填空题的具体HTML代码
')
});
}, "json");
});
$(document).ready(() => {
$("#submit").click(() => {
// 利用jQ获得作答的数据,并且Post给服务端
});
});
- $(document).ready(() => {
- $.get("/tests?id=" + id, (data) => { // 获得JSON格式的试题数据
- data.choice.forEach((item, index, array) => {
- $("#choice").append('\
- // 选择题的具体HTML代码
- ')
- });
- data.completion.forEach((item, index, array) => {
- $("#completion").append('\
- // 填空题的具体HTML代码
- ')
- });
- }, "json");
- });
- $(document).ready(() => {
- $("#submit").click(() => {
- // 利用jQ获得作答的数据,并且Post给服务端
- });
- });
$(document).ready(() => {
$.get("/tests?id=" + id, (data) => { // 获得JSON格式的试题数据
data.choice.forEach((item, index, array) => {
$("#choice").append('\
// 选择题的具体HTML代码
')
});
data.completion.forEach((item, index, array) => {
$("#completion").append('\
// 填空题的具体HTML代码
')
});
}, "json");
});
$(document).ready(() => {
$("#submit").click(() => {
// 利用jQ获得作答的数据,并且Post给服务端
});
});
习题数据查看脚本
$(document).ready(() => {
$.get("/tests?id=" + id, (data) => {
// 同样获得习题数据并展示
}, "json");
});
// 绘图
function plant(choiceArr, completionArr) {
choiceArr.forEach((element, index, array) => {
// 对每道选择题数据进行绘图
});
completionArr.forEach((element, index, array) => {
// 对每道填空题数据进行绘图
});
}
- $(document).ready(() => {
- $.get("/tests?id=" + id, (data) => {
- // 同样获得习题数据并展示
- }, "json");
- });
- // 绘图
- function plant(choiceArr, completionArr) {
- choiceArr.forEach((element, index, array) => {
- // 对每道选择题数据进行绘图
- });
- completionArr.forEach((element, index, array) => {
- // 对每道填空题数据进行绘图
- });
- }
$(document).ready(() => {
$.get("/tests?id=" + id, (data) => {
// 同样获得习题数据并展示
}, "json");
});
// 绘图
function plant(choiceArr, completionArr) {
choiceArr.forEach((element, index, array) => {
// 对每道选择题数据进行绘图
});
completionArr.forEach((element, index, array) => {
// 对每道填空题数据进行绘图
});
}
后端处理
习题数据
每一份习题在数据库中存储的形式:
_id | classname | name | choice | completion |
习题的唯一id(自动生成) | 习题发布的班级 | 习题名 | 所有选择题(数列) | 所有填空题(数列) |
每一道选择题的存储形式:
stem | A | B | C | D | answer | answers |
题干 | A选项内容 | B选项内容 | C选项内容 | D选项内容 | 标准答案 | 选择四个选项的学生名单 |
每一道填空题的存储形式:
stem | answer | answers |
题干 | 标准答案 | 回答正确学生名单、回答错误学生名单以及错误答案 |
路由配置
添加了一个tests.js路由文件,用来处理与试题相关的请求。
由于试题数据需要登录才能查看,并且需要分身份发送不同的内容,
几乎所有的响应都包含对是否已在session中登录和对身份是否符合的判断(在以下代码示例中略),
如果不符合,跳转至登录界面或者403界面。
// 直接返回试题原数据
router.get('/', function(req, res) {
db.find("test", {"_id": ObjectId(req.query.id)}, (data) => {
// 调用自己编写的调用数据库的函数,获得试题信息并返回
// 如果身份是学生,只截取
});
})
// 试题查看页面
router.get('/paper', function(req, res) {
db.find("test", {"_id": ObjectId(req.query.id)}, (data) => {
// 判断习题是否存在
// 习题若存在,如果session的identity是教师,发送教师的页面;如果是学生发送学生的页面
});
})
// 试题页面
router.get('/add', function(req, res) {
// 如果session的identity是教师,发送创建试题页面
})
// 上传新试题
router.post('/process_upload', function(req, res) {
// 将获得的新习题数据存入数据库
})
// 提交完成的试题
router.post('/process_submit', function(req, res) {
// 从数据库中取出相应的习题数据,将学生的作答插入数据中,再更新数据库
})
// 读取习题列表
router.get('/list', function(req, res) {
// 获得所有习题的列表
})
- // 直接返回试题原数据
- router.get('/', function(req, res) {
- db.find("test", {"_id": ObjectId(req.query.id)}, (data) => {
- // 调用自己编写的调用数据库的函数,获得试题信息并返回
- // 如果身份是学生,只截取
- });
- })
- // 试题查看页面
- router.get('/paper', function(req, res) {
- db.find("test", {"_id": ObjectId(req.query.id)}, (data) => {
- // 判断习题是否存在
- // 习题若存在,如果session的identity是教师,发送教师的页面;如果是学生发送学生的页面
- });
- })
- // 试题页面
- router.get('/add', function(req, res) {
- // 如果session的identity是教师,发送创建试题页面
- })
- // 上传新试题
- router.post('/process_upload', function(req, res) {
- // 将获得的新习题数据存入数据库
- })
- // 提交完成的试题
- router.post('/process_submit', function(req, res) {
- // 从数据库中取出相应的习题数据,将学生的作答插入数据中,再更新数据库
- })
- // 读取习题列表
- router.get('/list', function(req, res) {
- // 获得所有习题的列表
- })
// 直接返回试题原数据
router.get('/', function(req, res) {
db.find("test", {"_id": ObjectId(req.query.id)}, (data) => {
// 调用自己编写的调用数据库的函数,获得试题信息并返回
// 如果身份是学生,只截取
});
})
// 试题查看页面
router.get('/paper', function(req, res) {
db.find("test", {"_id": ObjectId(req.query.id)}, (data) => {
// 判断习题是否存在
// 习题若存在,如果session的identity是教师,发送教师的页面;如果是学生发送学生的页面
});
})
// 试题页面
router.get('/add', function(req, res) {
// 如果session的identity是教师,发送创建试题页面
})
// 上传新试题
router.post('/process_upload', function(req, res) {
// 将获得的新习题数据存入数据库
})
// 提交完成的试题
router.post('/process_submit', function(req, res) {
// 从数据库中取出相应的习题数据,将学生的作答插入数据中,再更新数据库
})
// 读取习题列表
router.get('/list', function(req, res) {
// 获得所有习题的列表
})
结语
这样便完成了一个基础的习题上传功能,在接下来还需要做功能的添加和优化,例如
- 增加题型
- 设置试题允许作答的起止时间、得分分布等
- 增加教师对学生作答内容的反馈功能
- 增加教师对习题的删除、锁定、修改等等动能
- 增加学生对习题的结果回查、排名查看
- 在从数据库取原作答情况,和更新新作答情况之间给数据库加Pessimistic Lock,以免学生同时提交作答导致数据丢失
- 模块化从试题数据库取试题所有数据、仅取题目、仅取学生作答情况和得分、仅取某一个学生的所有作答等功能
- 以及其他功能和优化