Node.js在线考试系统
1.布局总览
2.页面布局模块分析
1.navBar
页面中的导航栏
2.ready
考试前的准备界面,由三个部分组成
- 考试开始时间
- 考试倒计时
- 开始测试按钮
2.1 deltaTime
考试倒计时
2.2 startTest
开始测试按钮,时间到则允许开始考试
3.main
考试的主要界面,由四个部分组成
- 题目相关信息
- 题目内容
- 输入框
- 三个按钮
3.1questionInfo
题目相关信息,包括题目序号、题目分值、题目描述
3.2QuestionDetail
题目内容
3.3inputTe
答案输入框
3.4submit
由三个按钮组成,分别为切换到上一题、提交、切换到下一题
4.testInfo
考试相关信息,由两个部分组成
- 欢迎信息,例:Welcome!TOM!
- 题目编号,点击可跳转到相应题目
3.页面布局代码
准备界面
//省去html和body
<div class="navBar">
<div class="navBar-Content">
<span>Online Test Site</span>
<span style="align-self: flex-end;font-size:16px;margin-bottom: 10px;">For Student</span>
</div>
</div>
<div class="bc">
<div class="ready">
<span >考试10:00开始</span>
<span id="deltaTime"></span>
<button id="startTest" class="button button-highlight button-large">Go</button>
//button的样式用的是Buttons这个库
</div>
</div>
html{
height: 100%;
}
body{
margin: 0;
font-family: "Helvetica Neue",Helvetica,"Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
height: 100%;
-webkit-background-size: cover;
background-size: cover;
position: relative;
}
.navBar{
position: absolute;
top: 0;
height: 80px;
background: #FF6600;
display: flex;
justify-content: space-around;
z-index: 1;
min-width: 100%;
}
.navBar-Content{
width: 1000px;
height: inherit;
display: flex;
align-items: center;
justify-content:space-between;
}
.navBar-Content span{
color: #ffffff;
font-weight: 900;
font-size: 20px;
}
.bc{
display: flex;
width: 100%;
position: relative;
flex-direction: column;
height: 100%;
background: url(/images/bizhi1.jpg) no-repeat center;
min-width:1000px;
justify-content: space-around;
min-height: 800px;
}
.ready{
width: 300px;
height: 300px;
background: rgba(0,102,255,0.5);
align-self: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
.ready span{
color: #ffffff;
font-size: 20px;
}
答题界面
<div class="main">
<div class="questionInfo">
<span>第一题</span>
<span>分值:20分</span>
<span>题目描述:</span>
</div>
<span class="QuestionDetail"></span>
<div contenteditable="true" class="inputTe"></div>
<div class="submit">
<button id="preQuestion" class="button button-pill button-highlight"><i class="iconfont"></i>上一题</button>
<button id="submit" class="button button-pill button-primary">Submit</button>',
<button id="nextQuestion" class="button button-pill button-action"><i class="iconfont"></i>下一题</button>
</div>
</div>
<div class="testInfo">
<span class="welcome">Welcome!TOM!</span>
<div class="selectNumber">
<span class="coverLine">编号:</span>
<span class="Number">1</span>
<span class="Number">2</span>
<span class="Number">3</span>
<span class="Number">4</span>
<span class="Number">5</span>
</div>
</div>
.main{
padding:20px 50px 0 50px;
width: 800px;
display: flex;
flex-direction: column;
background: rgba(255,255,255,0.5);
align-self: center;
height: inherit;
overflow-y: auto;
margin-top: 80px;
}
.questionInfo{
display: flex;
flex-direction: column;
}
.questionInfo span{
margin-top: 5px;
}
.QuestionDetail{
background: #ffffff;
opacity: 0.8;
border: solid 1px #1B9AF7;
font-size: 18px;
}
.inputTe{
margin-top: 100px;
border: solid 1px #999999;
background:rgba(255,255,255,0.5);
flex: 0 0 350px;
word-break: break-all;
}
.submit{
margin-top: 40px;
display: flex;
justify-content: center;
justify-content:space-around;
flex: 1 0 40px;
padding-bottom: 20px;
align-items: end;
}
.testInfo .submit{
flex-direction: column;
margin: 0;
justify-content: space-around;
height: 150px;
flex: none;
align-items: stretch;
}
.testInfo{
position: fixed;
right: -240px;
top: 50%;
transform: translate(0,-50%);
width: 200px;
background: rgba(0,0,0,0.3);
transition: right 1.5s;
display: flex;
flex-direction: column;
padding: 30px 20px;
}
.testInfo .welcome{
color: #fff;
display: flex;
justify-content: center;
font-size: 18px;
align-items: center;
}
.selectNumber{
margin-top: 20px;
display: flex;
flex-wrap: wrap;
border: solid 1px #eee;
justify-content: space-around;
padding: 10px 0;
}
.coverLine{
flex: 1 0 100%;
padding-left: 15px;
}
.Number{
margin-top: 10px;
flex: 0 0 35px;
display: flex;
justify-content: center;
cursor: pointer;
background: #999999;
height: 35px;
align-items: center;
border-radius: 17.5px;
}
.Number:hover{
background: #bbbbbb;
}
.selectNumber span{
font-size: 18px;
color: #ffffff;
}
4.页面js代码
4.1准备界面:
用户登录到登录界面->创建modal()
模块->modal()
模块返回modalBox.init()
函数,init()
函数主要实现两个功能
1.计算出倒计时并渲染到界面上
2.绑定开始考试按钮的监听事件
var MODALBOX;
$(init);
function init() {
MODALBOX = new modal();
}
var modal = function () {
var ptrThis;
var body = document.getElementsByTagName('body'); //body
var questionInfo = document.getElementsByClassName("questionInfo");//题号+分值+(题目描述)
var QuestionDetail = document.getElementsByClassName("QuestionDetail");//题目详细内容
var testInfo = document.getElementsByClassName("testInfo");//右侧题目选择框
var modalBox = {
calculTime:function () {},
bindStartTest:function () {},
init:function () {
ptrThis = this;
ptrThis.calculTime(); //准备考试界面的倒计时
ptrThis.bindStartTest();//绑定开始考试按钮的监听事件
}
}
return modalBox.init();
}
计算考试倒计时
calculTime:function () {
var deltaTime = document.getElementById("deltaTime");
var goal = new Date("2016-11-28 22:00:00");//暂时没有实现后台功能,先用本地时间
var now = new Date();//调用js的Date类来获取时间
var deltaTotalTime = (goal.getTime()-now.getTime())/1000;//getTime以毫秒为单位,所以需要/1000才能获得秒
var deltaHours = Math.floor(deltaTotalTime/3600);//小时
var deltaMinutes = Math.floor((deltaTotalTime-3600*deltaHours)/60);//分钟
var deltaSeconds = Math.floor(deltaTotalTime-deltaHours*3600-deltaMinutes*60);//秒
deltaTime.textContent = "时间还剩"+deltaHours+"小时"+deltaMinutes+"分钟"+deltaSeconds+"秒";
setInterval(function () {//没过一秒重新设置一次
now = new Date();
deltaTotalTime = (goal.getTime()-now.getTime())/1000;
deltaHours = Math.floor(deltaTotalTime/3600);
deltaMinutes = Math.floor((deltaTotalTime-3600*deltaHours)/60);
deltaSeconds = Math.floor(deltaTotalTime-deltaHours*3600-deltaMinutes*60);
deltaTime.textContent = "时间还剩"+deltaHours+"小时"+deltaMinutes+"分钟"+deltaSeconds+"秒";
},1000)
}
绑定开始考试按钮的监听事件bindStartTest
bindStartTest:function () {
var startTest = document.getElementById("startTest");
startTest.addEventListener('click',function () {
var bc = document.getElementsByClassName("bc");//bc为界面的背景
var content = [
'<div class="main">',
' <div class="questionInfo">',
' <span>第一题</span>',
' <span>分值:20分</span>',
' <span>题目描述:</span>',
' </div>',
' <span class="QuestionDetail">',
' </span>',
' <div contenteditable="true" class="inputTe"></div>',
' <div class="submit">',
' <button id="preQuestion" class="button button-pill button-highlight"><i class="iconfont"></i>上一题</button>',
' <button id="submit" class="button button-pill button-primary">Submit</button>',
' <button id="nextQuestion" class="button button-pill button-action"><i class="iconfont"></i>下一题</button>',
' </div>',
'</div>'
].join("");
//通过getElementsByClassName获取到的dom元素返回的是数组,所以需要通过下标[0]来获取实际需要的元素
//页面采用js动态渲染,将bc原来的准备界面清空,然后将字符串转换为html重新给bc[0].innerHTML赋值
bc[0].innerHTML = content;
ptrThis.startTest();//将一系列监听事件绑定至界面元素
});
}
4.2答题界面:
用户点击GO触发按钮startTest
的点击事件->渲染界面->ptrThis.startTest()
->将一系列事件绑定,startTest实现的功能:
1.绑定输入框的监听事件
2.将题目选择框弹出
3.绑定选择问题监听事件
4.绑定上一题、提交、下一题按钮
var MODALBOX;
$(init);
function init() {
MODALBOX = new modal();
}
var modal = function () {
var ptrThis;
var body = document.getElementsByTagName('body'); //body
var questionInfo = document.getElementsByClassName("questionInfo");//题号+分值+(题目描述)
var QuestionDetail = document.getElementsByClassName("QuestionDetail");//题目详细内容
var testInfo = document.getElementsByClassName("testInfo");//右侧题目选择框
var modalBox = {
default:{
questions:[{
question:"读入一个自然数n,计算其各位数字之和,用汉语拼音写出和的每一位数字。",
score:20,
Chinese:"一"
},{
question:"读入n名学生的姓名、学号、成绩,分别输出成绩最高和成绩最低学生的姓名和学号。",
score:15,
Chinese:"二"
},{
question:"设计函数求一元多项式的导数。(注:xn(n为整数)的一阶导数为n*xn-1。)",
score:25,
Chinese:"三"
},{
question:"月饼是中国人在中秋佳节时吃的一种传统食品,不同地区有许多不同风味的月饼。现给定所有种类月饼的库存量、总售价、以及市场的最大需求量,请你计算可以获得的最大收益是多少。",
score:20,
Chinese:"四"
},{
question:"为了用事实说明挖掘机技术到底哪家强,PAT组织了一场挖掘机技能大赛。现请你根据比赛结果统计出技术最强的那个学校。",
score:20,
Chinese:"五"
}],
index:0//当前题目编号,
},
bindTextArea:function () {},
showTestInfo:function () {},
bindSelectQuestion:function () {},
bindButton:function () {},
toggleQuestion:function () {},
startTest:function () {
ptrThis.bindTextArea();//绑定输入框的监听事件
ptrThis.showTestInfo();//将题目选择框弹出
ptrThis.bindSelectQuestion();//绑定选择问题监听事件
ptrThis.bindButton();//绑定上一题、提交、下一题按钮
}
}
return modalBox.init();
}
绑定输入框的监听事件bindTextArea
该监听事件实现了当输入框代码行数太多时,页面下面的三个按钮会移动到testInfo模块上的功能,触发条件为
questionInfo(问题信息框)+QuestionDetail(问题内容框)+inputTe(输入框)+200的高度>main(外部框架)高度
bindTextArea:function () {
var inputTe = document.getElementsByClassName("inputTe");//输入框
var main = document.getElementsByClassName("main");//外部框架
var bindEvent = ["paste","keyup","keydown","resize"];//输入框需要监听粘贴,键盘升起,按下,输入框大小发生改变四个事件
var submit = document.createElement("div");//新建submit
submit.classList.add("submit");//添加submit类
submit.innerHTML = [' <button id="preQuestion" class="button button-pill button-highlight"><i class="iconfont"></i>上一题</button>',
'<button id="submit" class="button button-pill button-primary">Submit</button>',
'<button id="nextQuestion" class="button button-pill button-action"><i class="iconfont"></i>下一题</button>'].join("");
for (var i = 0;i<bindEvent.length;++i){
inputTe[0].addEventListener(bindEvent[i],function () {//循环给inputTe绑定事件
//clientHeight表示内容可视区域高度
var totalLength = 200+questionInfo[0].clientHeight+QuestionDetail[0].clientHeight+inputTe[0].clientHeight;
if(totalLength>main[0].clientHeight){
if(main[0].querySelector(".submit")!==null){
main[0].removeChild(main[0].querySelector(".submit"));
testInfo[0].appendChild(submit);
}
}else{
if(main[0].querySelector(".submit")===null){
main[0].appendChild(submit);
testInfo[0].removeChild(testInfo[0].querySelector(".submit"));
}
}
})
}
}
演示
绑定选择问题监听事件bindSelectQuestion
这个事件如果通过
jQuery
来实现就是$("body").on("click",".Number",function(){});
将事件绑定到body元素的好处是通过冒泡机制实现事件委托,不用担心元素发生改变后绑定事件失效的问题.详见http://www.cnblogs.com/aaronjs/p/3440647.html
bindSelectQuestion:function () {
body[0].addEventListener("click",function (e) {
if(e.target.classList.contains("Number")){
//调用indexOf获取当前点击对象位于父节点的子节点序列中的下标
ptrThis.default.index = [].indexOf.call(e.target.parentNode.children,e.target)-1;
//因为改变题目内容有很多地方用到,所以直接设置为一个函数
ptrThis.toggleQuestion();
}
},false);
},
重新设置题目相关数据函数toggleQuestion
这个函数修改了
questionInfo
和QuestionDetail
两部分的内容
toggleQuestion:function () {
questionInfo[0].children[0].textContent="第"+ptrThis.default.questions[ptrThis.default.index].Chinese+"题";
questionInfo[0].children[1].textContent="分值:"+ptrThis.default.questions[ptrThis.default.index].score;
QuestionDetail[0].textContent=ptrThis.default.questions[ptrThis.default.index].question;
},
绑定上一题、提交、下一题按钮监听事件bindButton
同样的事将监听事件绑定到body元素,然后通过getAttribute方法获取相应的id值来判断是哪一个按钮的点击事件
bindButton:function () {
body[0].addEventListener("click",function (e) {
var ID = e.target.getAttribute("id");
switch (ID){
case "preQuestion":
ptrThis.default.index = ptrThis.default.index==0?0:ptrThis.default.index-1;
break;
case "submit":
return;//还未实现与服务器后台的功能
case "nextQuestion":
ptrThis.default.index = ptrThis.default.index==4?4:ptrThis.default.index+1;
break;
default:
break;
}
ptrThis.toggleQuestion();
},false);
}
5.心得
新项目新思路,尝试用原生的js来完成各种功能虽然遇到了很多坑,但也收获到了不少知识.