这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战
考试系统主要包括教师端-老师出题和学生端-学生做题这两大主要功能,这是我开发的第一个vue项目,由于做项目时候是小白一个,写了很多low的代码,现在项目结束了,总结一下,温故而知新。 由于项目比较大,分几个章节来讲,今天先介绍一下学生端-学生做题的页面
如图,学生做题页面是传统的上下左右结构。header,left和right,下面我们主要讲做题的核心代码吧。包括了8种题型,分别是:qtype:1 单选题 2判断题 3多选题 4填空题 5连线题 6排序题 7问答题 8阅读理解(阅读理解比较特殊,7种类型题目的汇总)
这个界面主要实现了,学生进入做题页面,获取当前试卷的信息,渲染页面,开始考试倒计时,渲染左侧题型,默认加载第一种题型下的试题。学生选择其他题型,加载对应题型,加载过的题型不会重复加载,监控学生做题,左侧题目的序号变成已做,做题进度更新。学生点击左侧题的序号,加载滚动到对于题目。切屏超过3次自动提交试卷,切屏给与警告。提交试卷后,客观题自动批阅,主观题老师批阅。待成绩发放学生进来可以看到题目的正确与否,以及分数。本文先从学生做题界面开始介绍。
1.单选和判断用 el-radio
<div class="option">
<el-radio-group v-model="item.answerd" @change="chooseOptionFinal(itemP,item.indexNew,item,index,item.id)">
<el-radio :label="item2.id" :key="item2.id" v-for="item2 in item.options"><span class="spanFlex" v-html="item2.char+' '+item2.opName"></span></el-radio>
</el-radio-group>
</div>
2.多选用el-checkbox
<div class="option">
<el-checkbox-group v-model="item.answerd" @change="chooseOptionFinal(itemP,item.indexNew,item,index,item.id)">
<el-checkbox :label="item2.id" :key="item2.id" v-for="item2 in item.options"><span class="spanFlex" v-html="item2.char+' '+item2.opName"></span></el-checkbox>
</el-checkbox-group>
</div>
3.填空(老师出题时插入的input,做题时直接在input输入值)
<div class="stem" v-if="item1.sameScore" :ques-id="item.id">
<div v-html="item.stem"></div>
</div>
4.right部分代码
<el-main class="mainAll">
<el-collapse accordion v-model="chooseTab">
<el-collapse-item :class="`collapse${itemP.qtype}`" :name="itemP.qtype" v-for="(itemP,index) in questionTypes" :key="index">
<template slot="title">
<span class="fs16">{{itemP.qtype | typeChinese}}</span>
<i v-if="itemP.sameScore">(共{{itemP.qnum}}题,每题{{itemP.sameScore}}分,合计{{itemP.qscore}}分)</i>
<i v-else>(共{{itemP.qnum}}题,合计{{itemP.qscore}}分)</i>
</template>
<template v-if="itemP.qtype !== 8">
<div :id="`${item.id}`" :class="`subject subject${itemP.qtype}`" v-for="(item,index) in itemP.questions" :key="index">
<template v-if="itemP.qtype === 4"><!--填空题-->
<div class="stem" v-else :ques-id="item.id">
<div v-html="index+1+'、'"></div>
<div v-html="item.stem"></div>
<div v-html="'(' + (item.quesScore || 0) + '分)'"></div>
</div>
</template>
<template v-else>
<div class="stem" v-if="itemP.sameScore" v-html="index+1+'、'+item.stem" :ques-id="item.id"></div>
<div class="stem" v-else v-html="index+1+'、'+item.stem + '(' + (item.quesScore || 0) + '分)'" :ques-id="item.id"></div>
</template>
<template v-if="itemP.qtype === 1"><!--单选题-->
<div class="option">
<el-radio-group v-model="item.answerd" @change="chooseOptionFinal(itemP,index,item)">
<el-radio :label="item2.id" :key="item2.id" v-for="item2 in item.options">
<span class="spanFlex" v-html="item2.char+' '+item2.opName"></span>
</el-radio>
</el-radio-group>
</div>
</template>
<template v-if="itemP.qtype === 2 || itemP.qtype === 9"><!--多选题、不定项选择题-->
<div class="option">
<el-checkbox-group v-model="item.answerd" @change="chooseOptionFinal(itemP,index,item)">
<el-checkbox :label="item2.id" :key="item2.id" v-for="item2 in item.options">
<span class="spanFlex" v-html="item2.char+' '+item2.opName"></span>
</el-checkbox>
</el-checkbox-group>
</div>
</template>
<template v-if="itemP.qtype === 3"><!--判断题-->
<div class="option">
<el-radio-group v-model="item.answerd" @change="chooseOptionFinal(itemP,index,item)">
<el-radio :label="item2.id" :key="item2.id" v-for="item2 in item.options">
<span class="spanFlex" v-html="item2.opName"></span>
</el-radio>
</el-radio-group>
</div>
</template>
</div>
</template>
</el-collapse-item>
</el-collapse>
</el-main>
5.left部分
<el-aside width="260px" class="asideLeft">
<div class="aside_div">
<div class="oneItem">
<div class="fl">
<div class="fs14">试题总数</div>
<div><i class="fs28">{{answerData.questionNum}}</i><i class="fs12 co333">题</i></div>
</div>
<div class="fr">
<div class="fs14">试题总分</div>
<div><i class="fs28">{{answerData.paperScore}}</i><i class="fs12 co333">分</i></div>
</div>
</div>
<div class="twoItem pd20">
<p class="fs14">做题进度</p>
<el-progress :percentage="answerPro" :format="format"></el-progress>
<div>
<p class="fs14">剩余时间</p>
<div class="timeItem">
<div class="blackItem"></div>
<div class="blackItem"></div>
<span>{{hour? hourString:'00'}}</span>
</div>
:
<div class="timeItem">
<div class="blackItem"></div>
<div class="blackItem"></div>
<span>{{minute?minuteString:'00'}}</span>
</div>
:
<div class="timeItem">
<div class="blackItem"></div>
<div class="blackItem"></div>
<span>{{second?secondString:'00'}}</span>
</div>
</div>
</div>
<div :class="['testItem',chooseTab === item.qtype?'active':'']"
v-for="item in questionTypes" :key="item.qtype" @click="chooseTestTab(item.qtype)">
<div class="itemInner">
<div class="fs16">{{item.qtype | typeChinese}}</div>
<div class="fs12">共 {{item.qnum}} 题,<span v-if="item.sameScore && item.sameScore.toString().length>0">每题 {{item.sameScore}} 分,</span> 合计 {{item.qscore}} 分</div>
<template v-if="item.qtype===8">
<div v-for="item1 in item.radioList" :key="item1.index" style="margin-bottom: 10px">
<div class="fs12">{{item1.name}} (共 {{item1.num}} 题,合计 {{item1.score}} 分)</div>
<el-radio :id="`radio${item2.qid}`" @change="questionClick(item2.qid,item2)" :label="item2.label" border v-for="item2 in item1.radioList1" :key="item2.label" :class="[item2.flag===3?'redBorder':'',item2.flag===1?'greenBorder':'',item2.flag===2?'yellowBorder':'',item2.flag===4?'blueBorder':'',item2.clickFlag?'clickRect':'']"></el-radio>
</div>
</template>
<template v-else>
<el-radio :id="`radio${item2.qid}`" @change="questionClick(item2.qid,item2)" :label="item2.label" border v-for="item2 in item.radioList" :key="item2.label" :class="[item2.flag===3?'redBorder':'',item2.flag===1?'greenBorder':'',item2.flag===2?'yellowBorder':'',item2.flag===4?'blueBorder':'',item2.clickFlag?'clickRect':'']"></el-radio>
</template>
</div>
</div>
<div class="bottomItem pd20">
<i></i>未答题 <i></i>已答题
</div>
</div>
</el-aside>
6.js核心代码
data() {
return {
questionTypes: [],// 题库
answerCurrent: 0,// 做题数
answerPro: 0,// 做题进度
hour: '',// 时
minute: '',// 分
second: '',// 秒
}
}
watch:{
// 点击题型,展开对应题型,请求后台,获取题库,记录状态。加载过的不重复加载
chooseTab(newVal) {
this.questionTypes.forEach(item => {
if (item.qtype == newVal && item.questions.length===0) { // 没有加载该类型
this.getPaperInfos(); // 根据题型获取题库
}
});
},
// 题库
questionTypes: {
handler() {
this.answerCurrent = 0; // 已做试题总数
this.answerPro = 0;// 做题进度
this.questionTypes.forEach(item => {
item.radioList.forEach(itemRadio => {
if (commonJs.isNullVal(itemRadio.flag) && itemRadio.flag != 0) {
this.answerCurrent++; // 已做试题总数
this.answerPro++; // 做题进度
}
if (itemRadio.radioList1 && itemRadio.radioList1.length > 0) {
itemRadio.radioList1.forEach(itemRadio2 => {
if (itemRadio2.flag != 0) {
this.answerCurrent++; // 已做试题总数
this.answerPro++; // 做题进度
}
})
}
})
})
},
deep: true,
},
},
mounted() {
this.getPaperInfos();// 获取题库
},
methods:{
// 获取题库
getPaperInfos() {
// 请求后台接口,处理一些逻辑。
// 后台返回的考试时长
if (this.promiseTimer > 0) {// 分钟
this.hour = Math.floor((this.promiseTimer / 3600) % 24);
this.minute = Math.floor((this.promiseTimer / 60) % 60);
this.second = Math.floor(this.promiseTimer % 60);
this.countDown();/ 倒计时
}
},
//进度条
format() {
if (this.answerTotal) {
if(this.answerCurrent > this.answerTotal){
this.answerCurrent = this.answerTotal;
}
this.answerPro = (this.answerCurrent / this.answerTotal) * 100;
return this.answerCurrent + "/" + this.answerTotal;
} else {
this.answerPro = 0;
return 0;
}
},
// 考试倒计时
countDown() {
let self = this;
clearInterval(this.timeInterval);
//返回值是数字,从1开始
this.timeInterval = setInterval(function () {
self.costTime += 1;
if (self.hour === 0) {
if (self.minute !== 0 && self.second === 0) {
self.second = 59;
self.minute -= 1;
} else if (self.minute === 0 && self.second === 0) {
self.second = 0;
clearInterval(self.timeInterval);
self.saveOptionAll(3,'stop');
} else {
self.second -= 1
}
} else {
if (self.minute !== 0 && self.second === 0) {
self.second = 59;
self.minute -= 1
} else if (self.minute === 0 && self.second === 0) {
self.hour -= 1;
self.minute = 59;
self.second = 59
} else {
self.second -= 1
}
}
}, 1000);
},
// 题型的选择
chooseTestTab(key) {
this.chooseTab = key;
this.questionTypes.forEach((item,i)=> {
if(item.qtype !== 8){
item.radioList.forEach((itemRadio,r)=> {
this.$set(itemRadio, 'clickFlag',false);
})
}else{
item.radioList.forEach((itemRadio,r)=> {
itemRadio.radioList1.forEach((itemRadio1,r1)=> {
this.$set(itemRadio1, 'clickFlag',false);
})
})
}
})
},
}
以上是学生端-学生做题界面的第一部分。记录一下,温故而知新!