Files
learning-system-portal/src/components/Course/exam.vue

725 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="exam-index">
<div v-if="has" class="test-div">
<div v-if="!testStart">
<div>
<div class="test-info" v-if="content.contentName!=''">名称:{{content.contentName}}</div>
<div class="test-info">
<span >考试时长{{info.testDuration}}分钟</span>
<span style="margin-left: 20px;">及格线{{info.passLine}}</span>
<span v-if="records.length>0" style="margin-left: 20px;font-size: 18px;">您的考试成绩{{examScore}}</span>
</div>
</div>
<!-- 预览用显示试题 -->
<div v-if="showTest">
<div style="margin: 10px 0;">考试试题:</div>
<div v-for="(item,index) in viewTest" :key="index">
<div class="test-info">{{index +1}}.{{getTypeName(item.type)}}{{item.content}}</div>
<div class="test-info" style="padding-left: 20px;">
<div v-if="item.type==101 || item.type==103">
<el-radio-group v-model="item.userAnswer" size="medium">
<el-radio v-for="(opt,optIdx) in item.options" :key="optIdx" :label="opt.id">{{toLetter(optIdx+1)}}.{{opt.content}}</el-radio>
</el-radio-group>
</div>
<div v-if="item.type==102">
<el-checkbox v-for="(it,indcc) in item.options" :key="indcc" v-model="it.isCheck" :label="it.id">{{toLetter(indcc+1)}}.{{it.content}}</el-checkbox>
</div>
</div>
</div>
</div>
<!--开始考试button-->
<div v-if="showSubmit" style="text-align: center; margin-bottom: 15px">
<el-button type="primary" @click="startTest()">开始考试</el-button>
</div>
<!--考试记录-->
<div v-if="showRecord">
<el-table :data="records" style="width: 100%" border>
<el-table-column prop="lastTime" label="完成时间" align="center" ></el-table-column>
<el-table-column prop="testDuration" label="用时" align="center">
<template slot-scope="scope">{{formatSeconds(scope.row.testDuration)}}</template>
</el-table-column>
<el-table-column prop="score" label="成绩" align="center"></el-table-column>
<el-table-column label="操作" width="100" align="center">
<template slot-scope="scope">
<el-button @click="showExamAnswer(scope.row)" type="text" >查看试卷</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div v-else>
<!--考试部分-->
<div>
<div style="display: flex;justify-content: space-between;padding: 10px 0px;">
<div style="font-size: 18px;">{{curIndex+1}} / {{total}}</div>
<div style="font-size: 20px;"><span style="font-size: 40upx;" :class="{'redText':timerValue<5}">{{timerValue}}</span> 分钟</div>
</div>
<div class="qitem">
<div class="qitem-info">
<div>{{getTypeName(curItem.type)}}{{curItem.content}} </div>
<div class="qimg" v-if="curItem.images"><img class="qimg-fit" :src="imageBaseUrl+curItem.images"/></div>
</div>
<div v-for="(opt,optIdx) in curItem.options" :key="optIdx">
<div class="qitem-opts">
<div class="qitem-opt" :class="{check:opt.checked}" @click="chooseOption(opt)">
{{toLetter(optIdx+1)}}.{{opt.content}}
<i class="el-icon-check" v-if="opt.checked" name="checkbox-mark" color="#00aa00"></i>
</div>
<div class="qimg" v-if="opt.images"><img class="qimg-fit" :src="imageBaseUrl+opt.images"/></div>
</div>
</div>
</div>
<div style="text-align: center; margin-bottom: 15px">
<el-button :disabled="curIndex==0" type="primary" @click="prevSub()">上一题</el-button>
<el-button type="success" icon="el-icon-check" :disabled="!thisTrue" @click="present()"> </el-button>
<el-button :disabled="curIndex>=(total-1)" type="primary" @click="nextSub()">下一题</el-button>
</div>
</div>
</div>
</div>
<div v-else style="text-align: center;padding-top: 20px;color: red;">此课程无考试内容</div>
<el-dialog :visible.sync="detailShow" title="查询答卷详细信息" width="800px" custom-class="g-dialog">
<div style="text-align: left;" class="upaper">
<!--答卷信息-->
<div v-for="(ditem,didx) in detailItems" :key="didx" class="upaper-item">
<div class="upaper-item-q">
<div>{{didx +1}}.{{getTypeName(ditem.type)}}{{ditem.content}}</div>
<div class="qimg" v-if="ditem.images"><img class="qimg-fit" :src="imageBaseUrl+ditem.images"/></div>
</div>
<div class="upaper-item-opts" style="padding-left: 20px;">
<div v-for="(opt,optIdx) in ditem.options" :key="optIdx">
<div class="upaper-item-opt" :class="{'upaper-item-opt-user':ditem.userOptIdxs.indexOf(optIdx)>-1}">
<div>
<div>{{toLetter(optIdx+1)}}, {{opt.content}}</div>
</div>
<div>
<span v-if="ditem.userOptIdxs.indexOf(optIdx)>-1 && ditem.correctOptIdxs.indexOf(optIdx)>-1" style="color: #00aa00;font-size: 25px; "></span>
<span v-if="ditem.userOptIdxs.indexOf(optIdx)>-1 && ditem.correctOptIdxs.indexOf(optIdx)==-1" style="color: #ff0000;font-size: 25px; ">×</span>
</div>
</div>
<div class="qimg" v-if="opt.images"><img class="qimg-fit" :src="imageBaseUrl+opt.images"/></div>
</div>
</div>
<div class="upaper-item-answer" style="display: flex;">
<div class="upaper-item-answer-cell">
<span v-if="ditem.result" style="color: #00aa00; ">回答正确</span>
<span v-else style="color: #ff0000; ">回答错误</span>
</div>
<div class="upaper-item-answer-cell">
<span class="response-tit">正确答案</span>
<span v-for="op in ditem.correctOptIdxs" :key="op">{{toLetter(op+1)}}</span>
</div>
<div class="upaper-item-answer-cell">
<span class="response-tit">我的答案</span>
<span v-for="op in ditem.userOptIdxs" :key="op">{{toLetter(op+1)}}</span>
</div>
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="detailShow = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import apiStudy from '@/api/modules/courseStudy.js';
import apiCourse from '@/api/modules/course.js';
import apiExamPaper from '@/api/modules/paper.js';
import {formatDate,formatSeconds} from '@/utils/datetime.js';
import {testType,correctJudgment,numberToLetter} from '@/utils/tools.js';
export default {
props:{
studyId: {
type: String,
},
showRecord:{
type:Boolean,
default:true
},
showSubmit:{
type:Boolean,
default:true
},
content: {
type: Object,
default:()=>{}
},
showTest:{
type:Boolean,
default:false
}
},
data() {
return {
thisTrue: true,
viewTest:[],
imageBaseUrl:process.env.VUE_APP_FILE_BASE_URL,
correctJudgment:correctJudgment,
toLetter:numberToLetter,
formatSeconds:formatSeconds,
examScore:0,//考试得分
show:0,
getTypeName:testType,
has:true,
startTime:null,//开始考试的时间,可以去掉
testStart: false,//是否开始考试
info:{},//考试信息
paper:[],//试卷信息
studyItemId:'',
total:0,//总试题的数量
curIndex:0,//当前视频的索引
curItem:{},//当前试题
timer:null,//用于计算倒计时控制
lastScore:0,//最终得分,用于考试试卷记录
timerValue:0,//当前倒计时的值
noAnswers:[], //有未答完的试题
records:[] ,//考试记录
allowSubmit:true,//是否允许考试,尝试次数达到后不能再考试,暂时未使用
detailShow:false,
detailItems:[],
examPaper:{
json:{},//试题的json格式
items:[],//试题内容
}
};
},
mounted() {
this.loadExamInfo();
},
watch:{
content(newVal){
this.loadExamInfo();
}
},
destroyed() {
if(this.timer){
window.clearInterval(this.timer);
}
},
methods: {
changeTimer(){
if(this.timerValue<=0){
window.clearInterval(this.timer);
//系统自动提交
this.confirmStop();
}else{
this.timerValue--;
}
},
loadExamInfo(){
apiCourse.getExam(this.content.id).then(res=>{
if(res.status==200){
this.info=res.result;
if(this.showTest) {
let paper= JSON.parse(this.info.paperContent);
paper.items.forEach(item=>{
//console.log(item);
if(item.type==101){
item.userAnswer='';
}else if(item.type==102){
item.userAnswer=[];
}else{
item.userAnswer=''
}
item.options.forEach(opt=>{
opt.checked=false;
})
});
this.total=paper.items.length;
this.paper =paper;
//console.log(this.paper);
this.viewTest =paper.items;
}
if(!this.showTest && this.showRecord){
this.loadStudyItemId();
this.loadRecord();
}
}else if(res.status==404){
//没有找到考试信息
}else{
this.$message.error(res.message);
}
})
},
loadStudyItemId(){
//获取studyItemId;
apiStudy.getStudyContentItem(this.studyId,this.info.contentId).then(rs=>{
if(rs.status==200){
this.examScore=rs.result.score;
this.studyItemId=rs.result.id;
}
})
},
loadRecord(){
if(this.records.length==0){
let params={
studyId:this.studyId,
contentId:this.content.id
}
apiStudy.myExamList2(params).then(examRs=>{
if(examRs.status==200){
this.records=examRs.result;
let len=examRs.result.length;
if(this.info.times>len){
this.allowSubmit=true;
}else{
this.allowSubmit=false;
}
}
})
}
},
//独立考试的试题转化为课内考试的试题
convertToItems(questions){
let qitems=[];
questions.forEach(item=>{
let q={
id:item.id,
type:item.type==1? 101:item.type==2? 102:103,
score:item.defaultScore,
checked:false,
userAnswer:'',
optShow:true,
content:item.title,
images:item.images,
options:[]
}
if(item.type==3){
q.options.push({
id:item.id+'1',
images:'',
content:"正确",
answer:item.answer=='true'? true:false,
checked:false
});
q.options.push({
id:item.id+'2',
images:'',
content:"错误",
answer:item.answer=='true'? false:true,
checked:false
})
}else{
item.optionList.forEach(opt=>{
q.options.push({
id:opt.id,
images:opt.images,
content:opt.content,
answer:opt.isAnswer,
checked:false
})
});
}
if(q.type==102){
q.userAnswer=[];
}
qitems.push(q);
});
console.log(qitems,'qitems')
return qitems;
},
startTest(){
apiExamPaper.newPaperContent(this.info.id).then(res=>{
if(res.error == ''&&res.result != ''){
if(this.info.paperType==2){
this.examPaper.json=res.result;
let qitems=this.convertToItems(this.examPaper.json);
this.paper ={items:qitems};
this.total=qitems.length;
this.curItem=qitems[this.curIndex];
this.startTime=new Date();//记录开始时间
this.timerValue=this.info.testDuration;
this.timer=setInterval(this.changeTimer,60000);
this.testStart=true;
}else{
let paper= {items:res.result};
paper.items.forEach(item=>{
if(item.type==101){
item.userAnswer='';
}else if(item.type==102){
item.userAnswer=[];
}else{
item.userAnswer=''
}
item.options.forEach(opt=>{
opt.checked=false;
})
});
this.total=paper.items.length;
this.paper =paper;
this.curItem=paper.items[this.curIndex];
this.startTime=new Date();//记录开始时间
this.timerValue=this.info.testDuration;
this.timer=setInterval(this.changeTimer,60000);
this.testStart=true;
}
}else{
this.$message.error('加载试卷内容失败,请与管理员联系,试卷是否已删除');
}
})
// if(this.info.paperType==2){
// apiExamPaper.getPaperContent(this.info.paperId).then(rs=>{
// if(rs.status=200){
// this.examPaper.json=JSON.parse(rs.result);
// //console.log(this.examPaper.json,'this.examPaper.json');
// let qitems=this.convertToItems(this.examPaper.json);
// this.paper ={items:qitems};
// this.total=qitems.length;
// this.curItem=qitems[this.curIndex];
// this.startTime=new Date();//记录开始时间
// this.timerValue=this.info.testDuration;
// this.timer=setInterval(this.changeTimer,60000);
// this.testStart=true;
// }else{
// this.$message.error('加载试卷内容失败,请与管理员联系,试卷是否已删除');
// }
// })
// }else{
// let paper= JSON.parse(this.info.paperContent);
// paper.items.forEach(item=>{
// //console.log(item);
// if(item.type==101){
// item.userAnswer='';
// }else if(item.type==102){
// item.userAnswer=[];
// }else{
// item.userAnswer=''
// }
// item.options.forEach(opt=>{
// opt.checked=false;
// })
// });
// this.total=paper.items.length;
// this.paper =paper;
// //console.log(this.paper);
// this.curItem=paper.items[this.curIndex];
// this.startTime=new Date();//记录开始时间
// this.timerValue=this.info.testDuration;
// this.timer=setInterval(this.changeTimer,60000);
// this.testStart=true;
// }
},
chooseOption(opt){
if(this.curItem.type==101 || this.curItem.type==103){
this.curItem.options.forEach(op=>{
op.checked=false;
})
opt.checked=true;
}else{
if(opt.checked){
opt.checked=false;
}else{
opt.checked=true;
}
}
},
confirmStop(){
this.$alert('考试时间已到,点击确定提交试卷', '提示', {
confirmButtonText: '确定',
showClose:false,
callback: action => {
this.submitTest();
}
});
},
prevSub(){ //上一题
if(this.curIndex==0){
return;
}
this.curIndex--;
if(this.curIndex>-1){
this.curItem=this.paper.items[this.curIndex];
}
},
nextSub() { //下一题
if(this.curIndex>=(this.total-1)){
return;
}
this.curIndex++;
this.curItem=this.paper.items[this.curIndex];
},
throttle(func, delay) {
let lastExecTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastExecTime >= delay) {
func.apply(this, args);
lastExecTime = now;
}
};
},
present(){ //提交前处理
let $this=this;
let score=this.countTest();
console.log('score='+score);
if(this.noAnswers.length>0){
this.$confirm('还有未答试题,您确定要提交吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
showClose:false,
type: 'warning'
}).then(() => {
this.submitTest(score);
}).catch(()=>{
})
}else{
this.submitTest(score);
}
},
countTest(){ //计算考试的分数
//console.log(this.paper.items);
let totalScore=0;
this.paper.items.forEach(item => {
item.score=parseInt(item.score);
//console.log(item.score,'item.score');
totalScore+=item.score;//加到总分中
if(item.type != 102){
item.userAnswer='';
item.options.forEach(opt => {
if(opt.checked){
item.userAnswer=opt.id;
}
});
}else{
item.userAnswer=[];
item.options.forEach(opt => {
if(opt.checked){
item.userAnswer.push(opt.id);
}
});
}
});
//console.log(totalScore,'totalScore');
let scoreNum = 0;//用户得分
let noAnswers=[];//是否都已答
this.paper.items.forEach((item,idx) => {
if(item.type != 102){
if(item.userAnswer==''){
noAnswers.push(idx+1);
}
item.options.forEach(it => {
if(it.answer && item.userAnswer == it.id) {
scoreNum+=item.score
}
})
}else{
if(item.userAnswer.length==0){
noAnswers.push(idx+1);
}
let allRight = true;
//console.log('用户的答案',item.userAnswer);
item.options.forEach(it =>{
//console.log('选项',it.answer,it.id,item.userAnswer.indexOf(it.id));
if(it.answer){ //正确答案
if(item.userAnswer.indexOf(it.id)==-1){
allRight=false;
}
}else{
if(item.userAnswer.indexOf(it.id)>-1){
allRight=false;
}
}
});
//console.log('是否回答正确',allRight)
if(allRight){
scoreNum+=item.score;
}
}
})
this.noAnswers=noAnswers;
if(scoreNum === null)scoreNum=0;
this.lastScore=scoreNum;
//转化成百分制显示
//console.log(scoreNum,totalScore,'实际分和总分');
if(this.info.percentScore){
this.lastScore=parseInt(scoreNum*100/totalScore);
}
//console.log('本次得分='+this.lastScore);
return this.lastScore;
},
submitTest(testScore){ //提交处理
//清空提示
if(this.timer){
window.clearInterval(this.timer);
}
let now=new Date();
if(!testScore){
testScore=this.countTest();
}
let postData={
studyId:this.studyId,//
studyItemId:this.studyItemId,//前面已经给了
courseId:this.content.courseId,
contentId:this.content.id,
testId:this.info.id,
testName:''+this.content.contentName,//应该是课程的名称 + 内容的名称
testDuration:0,
arrange:this.info.arrange,
passLine:this.info.passLine,
randomMode:this.info.randomMode,
score:testScore,
paperJson:JSON.stringify(this.paper),//原来是对象,这里要也要对象
startTime:formatDate(this.startTime),//此时间需要格式化,格式化时间可以放在util中
//endTime:formatDate(now),
}
//计划考试的时长
var dateDiff = now.getTime() - this.startTime.getTime();//时间差的毫秒数
var minutes=Math.floor(dateDiff/(1000))//计算相差秒数分钟记录的太大经常为0
postData.testDuration=minutes;
this.thisTrue = false
apiStudy.saveExam(postData).then(res=>{
this.thisTrue = true
if(res.status == 200) {
this.records.push(res.result);
this.content.status=9;//表已学习完,判断上级的章是否已完成
this.studyItemId=res.result.studyItemId;//第一次保存时是没有的,所以这里要赋值
this.$alert('您本次考试得分:'+this.lastScore, '考试成绩', {
confirmButtonText: '确定',
callback: action => {
this.testStart = false;
}
});
this.testStart = false;
//获取最新的成绩
this.loadStudyItemId();
this.$emit('submit',this.lastScore);//考试提交回调处理
} else {
this.$message.error(res.message);
}
})
},
showExamAnswer(item){
//显示试卷的答卷信息
this.detailShow=true;
apiStudy.myExamPaper(item.id).then(rs=>{
if(rs.status==200){
let items=JSON.parse(rs.result.paper).items
items.forEach((item)=>{
item.correctOptIdxs=[];
item.userOptIdxs=[];
item.result=true;
//对答案进行判断
item.options.forEach((opt,idx)=>{
//填充正确答案
if(opt.answer){
item.correctOptIdxs.push(idx);
}
if(item.type!=102){ //单选或判断
if(opt.id==item.userAnswer){
item.userOptIdxs.push(idx);
}
}else{ //多选
if(item.userAnswer.indexOf(opt.id)>-1){
item.userOptIdxs.push(idx);
}
}
});
//判断答案是否正确
if(item.correctOptIdxs.toString()==item.userOptIdxs.toString()){
item.result=true;
}else{
item.result=false;
}
});
this.detailItems=items;
}
});
},
},
// created() {
// this.debouncedPresent = this.debounce(this.present, 500);
// },
}
</script>
<style scoped lang="scss">
.qitem {
padding: 10px 20px;
.qitem-info {
font-size: 1.2em;
padding: 10px 0px;
}
.qitem-opts {
padding: 5px 0px;
.qitem-opt {
cursor: pointer;
display: flex;
margin-bottom: 10px;
padding: 10px 15px;
background-color: #FFFFFF;
border-radius: 6px;
}
.check{
color:#00aa00;
}
}
}
.upaper{
text-align: left;
.upaper-item{
border-bottom: 1px solid #dadada;
padding: 10px;
.upaper-item-q{
padding: 8px 0px;
}
.upaper-item-opts{
padding: 8px 0px;
line-height: 20px;
.upaper-item-opt{
min-height: 50px;
border-radius: 4px;
display: flex;
background-color: #FFFFFF;
justify-content: space-between;
padding: 10px 20px;
}
.upaper-item-opt-user{
background-color: #fff3e5;
}
}
.upaper-item-answer{
display: flex;
.upaper-item-answer-cell{
margin-right: 20px;
}
}
}
}
.test-div {
border: 1px solid #dadada;
min-height: 500px;
padding: 20px;
font-size: 14px;
text-align: left;
.test-info {
text-align: left;
margin-bottom: 10px;
}
}
.qimg{
padding-left: 30px;
width:100%;
text-align: left;
.qimg-fit{
width:100%;
object-fit:scale-down
}
}
</style>