Node.js在线考试系统——页面布局(学生)

Node.js在线考试系统

1.布局总览

准备界面.jpg
答题界面.jpg

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"));
                        }

                    }
                })
            }
        }

演示

输入框监听事件.gif

绑定选择问题监听事件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

这个函数修改了questionInfoQuestionDetail两部分的内容

        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来完成各种功能虽然遇到了很多坑,但也收获到了不少知识.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容