工具:Node.js + MongoDB + Socket.IO
完成進度
教師端:
- 學生的添加刪除等操作
- 考題和分數的添加刪除編輯修改等操作
- 在查看考試情況頁面顯示所有考生的姓名和學號,以及狀態信息(紅色:已登錄;灰色:未登錄;藍色:考試中;綠色:已交卷)
- 點擊已提交的考生對象,進入該考生的閱卷界面,顯示該考生提交考卷和答案的信息,并且可以批閱該考卷的得分;
- 實時顯示考生的狀態
學生端:
- 考生登入系統后,若時間未到,顯示倒計時,點擊題號彈出警告框;若時間到,可進行答題
- 點擊題號后進入答題狀態,同時教師端會實時顯示考生的狀態;
- 點擊其他題號答題自動保存;
- 考試時間到自動提交等;
預覽
教師端實時顯示考生狀態
教師端界面
學生端界面
1. 數據結構定義
1.用戶表
用戶學號
userId
,姓名username
, 密碼password
, 類型category
(學生/老師), 狀態status
(初始,登錄,答題,提交)
var userSchema = new Schema({
userId: String,
username: String,
password: String,
category: String, //分類-學生
status: String, //狀態
meta: {
updateAt: {type:Date, default: Date.now()},
createAt: {type:Date, default: Date.now()}
}
});
2.答題表
題目內容
content
,分數score
var questionSchema = new Schema({
content: String,
score: Number,
meta: {
updateAt: {type:Date, default: Date.now()},
createAt: {type:Date, default: Date.now()}
}
});
3. 學生答題內容表
學生ID
userId
,問題IDquestionId
,回答內容answerCtn
, 批閱后得到的分數score
var answerSchema = new Schema({
userId: {type: ObjectId, ref: 'User'},
questionId: {type: ObjectId, ref: 'Question'},
answerCtn: String,
score: Number,
meta: {
updateAt: {type:Date, default: Date.now()},
createAt: {type:Date, default: Date.now()}
}
});
2. 教師端模塊分解
2.1 學生管理
- 學生列表:查看已添加的學生學號和姓名
- 添加學生:添加新學生
2.2 題目管理
- 查看題目列表:點擊題號顯示保存的題目內容和分數,點擊文本框修改內容
- 添加題目:添加新題目和分數
2.3 考試情況
- 學生考試狀態:
- 實時查看學生的各種狀態信息(紅色:已登錄;灰色:未登錄;藍色:考試中;綠色:已交卷)
- 可點擊已交卷的學生塊,進行對該學生的閱卷操作
- 學生考試成績:查看學生的考試成績信息
3. 學生端模塊分解
3.1 倒計時模塊
倒計時模塊
- 未到達開考時間顯示 “距離考試開始” 的倒計時;
- 到達開考時間顯示 “距離考試結束” 的倒計時,直到考試結束倒計時停止;
3.2 答題模塊
- 若考試時間未到點擊題號,彈出警告框(考試時間未到);
- 考試時間到學生點擊題號進入答題狀態,教師端更新學生狀態;
- 考試未結束考生點擊提交或者考試時間到,考生轉換成提交狀態,教師端更新學生狀態,提交狀態的考生無法繼續答題;
4. 模塊代碼分析
4.1 登錄檢測
用戶類型分為考生和教師,在登錄時檢測用戶的類型,如果是教師則登入教師端頁面,如果是考生則進入考生頁面。
// result為登錄成功返回的用戶信息
if (result.data.category === "TEACHER") {
location.href = "/p/index";
} else {
location.href = "/p/indexStudent";
}
4.2 添加學生(添加題目方法類似)
添加學生頁面元素
//postData()為之后的post提供函數
function postData(url, data, cb) {
var promise = $.ajax({
type: "post",
url: url,
dataType: "json",
contentType: "application/json",
data:data
});
promise.done(cb);
}
//傳遞JSON
function doAddStudent() {
var jsonData = JSON.stringify({
'usrId': usrId,
'pwd': pwd,
'username': username
});
postData(urlAddStudent, jsonData, cbAddStudent);
}
//返回結果
function cbAddStudent(result) {
if (result.code == 99) {
alert(result.msg);
} else {
alert("添加成功!");
location.href = '/p/index';
}
}
4.3 閱卷(查看考題信息和學生答題模塊方法類似)
進入頁面通過POST從數據庫獲得題目列表,渲染出題號列表,每個題號給予一個
data-id
post獲取題目列表,通過$.format(QUESTION_LIST, list[i]._id, i+1);
渲染每一個題號,添加到(".item-number"
里;
QUESTION_LIST模板
var QUESTION_LIST = "<div class='question-item' data-toggle='select' data-id='{0}'>{1}</div>";
//獲取題目列表
function getQuestionList() {
var jsonData = JSON.stringify({});
postData(urlGetQuestionList, jsonData, cbQuestionList);
}
function cbQuestionList(result) {
var list = result.results;
for(var i = 0; i < list.length; i++) {
var html = $.format(QUESTION_LIST, list[i]._id, i+1);
$(".item-number").append(html);
}
}
點擊題號獲得data-id
$("body").on("click", "[data-toggle='select']", showContent);
//顯示題目內容和學生答題內容
function showContent(e) {
$(".answer-wrap").removeClass("hide");
e.preventDefault();
var $this = $(this);
questionId = $this.data('id');
$("#question-head").text("第" + $(this).text() + "題");
getQuestionCtn();
getAnswerOne();
if(questionId != 0) {
saveMark();
}
}
post獲取題目內容,返回結果放到指定div內
$("#questionContent").text(result.content);
//獲取題目內容
function getQuestionCtn() {
var jsonData = JSON.stringify({
"_id": questionId
});
postData(urlGetQuestionCtn, jsonData, cbShowQuestionCtn);
}
function cbShowQuestionCtn(result) {
$("#que-score").text("分值:" + result.score);
$("#questionContent").text(result.content);
}
post獲取學生答題內容,返回的結果放到指定div內
$("#answerCtn").text(result.answerCtn);
//獲得學生答案
function getAnswerOne() {
var jsonData = JSON.stringify({
"userId": studentId,
"questionId": questionId
});
postData(urlGetAnswerOne, jsonData, cbShowAnswer);
}
function cbShowAnswer(result) {
if(result != "99") {
$("#give-score").val(result.score);
$("#answerCtn").text(result.answerCtn);
} else {
$("#answerCtn").text("該學生沒有完成該題目");
}
}
4.4 考生端倒計時
- 將教師設定的開考時間和結束時間分別與當前時間比較,得到相差的時間差毫秒
seconds
。對seconds
進行處理得到格式化的字符串表示時間。
- 若當前時間小于開考時間,顯示距離考試開始倒計時,到時間
seconds <= 0
,進入答題倒計時,顯示距離考試結束倒計時,直到seconds <= 0
,停止倒計時并自動提交考卷,考生轉換成提交狀態SUBMIT
;
function getTimeDifference(y, n, M, h, m) {
var now = new Date();
var startTime = new Date(y, n, M, h, m);
var timeDifference = startTime.getTime() - now.getTime();
var second = parseInt(timeDifference / 1000);
var time = {
remain: second,
second: (second < 60) ? second : second % 60,
hour: parseInt(second / 3600),
minute: parseInt((second - parseInt(second / 3600) * 3600) / 60)
};
return time;
}
//考試開始時間
function timeBefore() {
var timer = setInterval(function() {
var time = getTimeDifference(2016, 10, 24, 18, 56);
$('#time-title').text("距離考試開始");
$('#time-ctn').text(time.hour + " : " + time.minute + " : " + time.second);
if(time.remain <= 0) {
status = START;
showExamTime();
clearInterval(timer);
}
}, 1000);
}
//考試結束倒計時
function showExamTime() {
var timer = setInterval(function() {
var time = getTimeDifference(2016, 10, 24, 23, 59);
$('#time-title').text("距離考試結束");
$('#time-ctn').text(time.hour + " : " + time.minute + " : " + time.second);
if(time.remain <= 0) {
status = END;
doUpdate(SUBMIT);
clearInterval(timer);
}
}, 1000);
}
5. 將考生端的考生狀態實時更新到教師端
- 考生登錄系統發送帶有用戶ID
userId
和用戶類型category
的login
消息給服務器,服務器保存該用戶(user[userId] = socket
),接著判斷該用戶是否為教師,若是則保存teacherId
;最后在數據庫中將該用戶狀態更新為登錄LOGIN
狀態,向教師端發送reload
消息,教師端接收到后重新post獲取學生狀態;
- 開考時間到,考生處于可考試狀態
WAIT
,考生點擊題號轉換成答題狀態EXAM
,post到數據庫更新狀態EXAM
,同時向服務器發送狀態轉換消息update status
,服務器接收到后向教師端發送reload
消息`; - 考生點擊提交按鈕,數據庫更新狀態
SUBMIT
,同時向服務器發送狀態轉換消息update status
,服務器接收到后向教師端發送reload
消息`;
考生端
//socket初始化
function socketInit() {
var data = {
userId: userId,
userCategory: userCategory
};
socket.emit("login", data);
status = LOGIN;
}
function showQuestion(e) {
//如果為開考狀態且用戶不處于提交狀態
if(status == START && userStatus != SUBMIT) {
getQuestionCtn(); //獲取題目內容
getAnswerOne(); //獲取保存的答題內容
doUpdate(EXAM); //轉換為答題狀態
} else if(userStatus == SUBMIT) {
alert("你已提交答卷,請等候老師批閱。");
} else if(status != START) {
alert("考試時間未到!");
}
}
//獲取題目列表
function getQuestionCtn() {}
function cbShowQuestionCtn(result) {}
//保存答題內容
function doSaveAnswer() {}
//獲取答題保存的內容
function getAnswerOne() {}
function cbShowAnswer(result) {}
//數據庫更新用戶狀態SUBMIT,向教師端發送reload消息
function doUpdate(status) {
socket.emit("update status");
}
//轉換成SUBMIT狀態
function cbUpdateStatus(result) {
userStatus = SUBMIT;
}
服務器
io.on('connection', function(socket){
//用戶登錄
socket.on('login', function (data) {
socket.name = data.userId;
user[data.userId] = socket;
var data2 = {
userId: socket.name,
status: "LOGIN"
};
dbHelper.updateStatus(data2, function (success, doc) {});
//用戶類型-老師
if(data.userCategory === "TEACHER") {
teacherId = data.userId;
}
//向老師的客戶端發送重新加載命令
if(teacherId !== 0) {
user[teacherId].emit("reload");
}
});
socket.on('update status', function () {
if(teacherId !== 0) {
user[teacherId].emit("reload");
}
});
//用戶退出
socket.on('disconnect', function () {
var data = {
userId: socket.name,
status: "INIT"
};
dbHelper.updateStatus(data, function (success, doc) {});
if(socket.name === teacherId) {
teacherId = 0;
} else if(teacherId !== 0){
user[teacherId].emit("reload");
}
delete user[socket.name];
});
});