Files
learning-system-mobile/pages/exam/exam.vue
2024-06-25 19:36:28 +08:00

848 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>
<!--考试页面用户在此页面一个试题一个试题的回答-->
<view>
<u-toast ref="messager"></u-toast>
<view style="text-align: center;padding: 10px;"><text class="title-name">{{testPaper.testName}}</text></view>
<view class="epage" v-if="testStatus ==1">
<view class="">
<u-cell-group>
<u-cell size="large" title="答题时间" :value="testPaper.testDuration+ '分钟'"></u-cell>
<u-cell size="large" title="答题次数" :value="testPaper.times + '次'"></u-cell>
<u-cell size="large" title="及格线" :value="testPaper.passLine"></u-cell>
<u-cell v-if="testPaper.entranceTime" size="large" title="入场时间" :value="testPaper.entranceTime"></u-cell>
<u-cell v-if="testPaper.deadlineTime" size="large" title="结束时间" :value="testPaper.deadlineTime"></u-cell>
</u-cell-group>
<view class="" style="padding: 20upx;" v-if="testPaper.testFront">
{{testPaper.testFront}}
</view>
<view class="sta-btn" v-if="canExam">
<view v-if="examStatus==0" style="text-align: center;color:#6d6d6d; ">考试还未开始</view>
<view v-if="examStatus==1">
<view v-if="tipText!=''" style="text-align: center;color:red">
<text>{{tipText}}</text>
</view>
<view v-else style="text-align: center;">
<view v-if="testPaper.enabled">
<u-button type="primary" :disabled="startButton" v-if="testStatus == 1" @click="startTest()">{{btnText}}</u-button>
</view>
<view v-else style="color: #666666;">此考试已下架</view>
</view>
</view>
<view v-if="examStatus==2" style="text-align: center;color:#6d6d6d; ">考试已结束</view>
</view>
<view class="" style="padding: 20upx;" v-if="canExam && tableData.length>0">
<view class="" style="margin-bottom: 20upx;">
考试记录
</view>
<view class="history">
<view v-for="(item, index) in tableData" :key="index" class="history-item">
<view class="history-row">
<view>{{item.lastTime}}</view>
<view>
<text v-if="item.status==1" style="color: #ff0000;">未提交</text>
<text v-if="item.status==8">中断</text>
<text v-if="item.status==9" style="color: #00aa00; ">完成</text>
</view>
</view>
<view class="history-row">
<view>得分{{toScoreTow(item.score)}}</view>
<view>
<text v-if="item.status<9 && examStatus==1" @click="reStartTest(item)">继续考试</text>
<text v-else @click="toShowAnswer(item)">查看试卷</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view v-if="testStatus ==2">
<view style="display: flex;justify-content: space-between;padding: 20upx;">
<view style="padding-top: 10upx;color: #757575; ">{{curIndex+1}} / {{total}}</view>
<view style="color: #000000;">
<text style="font-size: 40upx;" :class="{'redText':remainingTime<5}">{{formatSeconds(remainingTime)}}</text>
</view>
</view>
<!--试题内容-->
<view class="qitem">
<view class="qitem-info">
<view>[{{getQuestionType(curItem.type)}}]{{curItem.title}}</view>
<view v-if="curItem.images" class="qimg">
<img class="qimg-fit" :src="imageBaseUrl+curItem.images"/>
</view>
</view>
<view v-if="curItem.type == 3">
<view class="qitem-opts">
<view class="qitem-opt" :class="{check:curItem.userAnswer == 'true'}"
@click="chooseOption(curItem,true)">
{{toLetter(1)}}.正确
<u-icon v-if="curItem.userAnswer == 'true'" name="checkbox-mark" color="#00aa00"></u-icon>
</view>
<view class="qitem-opt" :class="{check:curItem.userAnswer=='false'}"
@click="chooseOption(curItem,false)">
{{toLetter(2)}}.错误
<u-icon v-if="curItem.userAnswer=='false'" name="checkbox-mark" color="#00aa00"></u-icon>
</view>
</view>
</view>
<view v-else>
<view v-for="(opt,optIdx) in curItem.optionList" :key="opt.id">
<view class="qitem-opts" v-if="curItem.type == 1">
<view class="qitem-opt" :class="{check:opt.id==curItem.userAnswer}" @click="chooseOption(opt)">
{{toLetter(optIdx+1)}}.{{opt.content}}
<u-icon v-if="opt.id==curItem.userAnswer" name="checkbox-mark" color="#00aa00"></u-icon>
</view>
<view v-if="opt.images" class="qimg">
<img class="qimg-fit" :src="imageBaseUrl+opt.images"/>
</view>
</view>
<view class="qitem-opts" v-if="curItem.type == 2">
<view class="qitem-opt" :class="{check:(curItem.userAnswer && curItem.userAnswer.indexOf(opt.id) > -1)}" @click="chooseOption(opt)">
{{toLetter(optIdx+1)}}.{{opt.content}}
<u-icon v-if="curItem.userAnswer && curItem.userAnswer.indexOf(opt.id) > -1" name="checkbox-mark" color="#00aa00"></u-icon>
</view>
<view v-if="opt.images" class="qimg">
<img class="qimg-fit" :src="imageBaseUrl+opt.images"/>
</view>
</view>
</view>
</view>
</view>
<view style="height: 150px;"></view>
<view class="bottom-btns">
<u-button class="next" v-if="curIndex==(total-1)" type="success" text="提交" :disabled="!thisTrue" @click="manualSubmit"></u-button>
<u-button type="primary" text="下一题" @click="nextSub" class="next" v-if="curIndex<(total-1)"></u-button>
<u-button type="info" text="上一题" @click="prevSub" class="next" v-if="curIndex>0"></u-button>
</view>
</view>
<view class="" v-if="testStatus ==3">
<view class="pager-title">
</view>
<view class="score-box">
考试已结束得分 {{toScoreTow(score)}}
</view>
<view class="sta-btn">
<u-button style="border-radius: 36upx;" text=" 关 闭 " @click="closeTest()" type="primary"></u-button>
</view>
</view>
<u-modal :show="stop" @confirm="confirmStop" @cancel="cancelStop" ref="stopModal"
content="时间已到,系统自动提交" confirmText="是" :showCancelButton="false"
:asyncClose="true">
</u-modal>
<u-modal :show="scoreShow" @confirm="confirmFinish" title="您本次得分" :content="''+lastScore">
</u-modal>
</view>
</template>
<script>
import config from '@/config/index.js'
import apiTestPaper from '@/api/modules/testPaper.js'
import { correctJudgment, numberToLetter, examType,toScoreTow } from '@/utils/tools.js';
import { formatDate, formatSeconds } from '@/utils/index.js';
import { mapGetters } from 'vuex';
export default {
data() {
return {
thisTrue: true,
toScoreTow,
startButton: false,
imageBaseUrl:config.fileUrl,
examId: '', //考试的id
taskId: '', //考试任务的id
lastId: '', //最后一次提交的答卷
canExam: false, //能否参加考试
tipText: '', //提示信息
examStatus: 0, //0表无1表考试中2表已结束
btnText: '开始考试',
handleSaveTest: null, //对应timout定时任务的指向
getTypeName: examType,
userInfo: {},
formatDate,
testPaper: {
id: '',
testName: '', //考试名称
testDuration: null, //考试时长
showAnalysis: false, //显示解析
showAnswer: false, //显示答案
times: null, //尝试次数 默认是0无限制
arrange: null, //试题排列 1试题乱序2选项乱序3全部乱序
scoringType: null, //评分方式 1最高一次2最后一次
passLine: null, //及格线
randomMode: false, //随机模式
randomCount: null, //随机数量
testType: null, //考试的类型* 1测试模式 2练习模式
publishTime: '', //发布时间
testRemark: '', //考试描述
testFront: '', //考前描述
testUp: '', //考后描述
entranceTime: '', //入场时间
paperId: '', //试卷的ID
paperContent: '', //试卷内容
},
aloneExamAnswerId: null, // 考试提交答案ID
testStatus: 1, // 1考前 2考中 3考后
reckonTimeer: null, // 计时器
score: 0,
studyId: '',
remainingTime: 0,
ctype: 10,
startTime: null,
toLetter: numberToLetter,
info: {},
stop: false,
paper: [],
total: 0,
curIndex: 0,
curItem: {},
timer: null,
scoreShow: false,
lastScore: 0,
timerValue: 0,
noAnswers: [], //有未答完的试题
tableData: [],
}
},
onLoad(options) {
this.examId = options.id;
this.$store.dispatch('GetUserInfo').then(rs => {
this.userInfo = rs;
});
if (this.examId) {
this.loadData();
}
},
methods: {
loadData() { //加载考试信息
apiTestPaper.getTestInfo(this.examId).then(res => {
if (res.status == 200) {
this.canExam = res.result.hasTask;
this.examStatus = res.result.examStatus;
this.testPaper = res.result.exam;
this.taskId = res.result.taskId;
if (res.result.hasLast && res.result.lastStatus < 9) {
this.lastId = res.result.lastId;
}
if(!this.testPaper.published){
this.tipText="此考试已取消,请与管理员联系";
}
//转换testDuration为秒,此转化在点击开始考试时才应该执行
this.remainingTime = this.testPaper.testDuration * 60;
//加载考试记录
if (res.result.hasTask) {
if (res.result.hasLast) {
this.btnText = '重新考试';
this.testAnswers();
}
}
} else {
// this.$message.error(res.message);
}
});
},
testAnswers() {
apiTestPaper.myTestAnswers(this.examId).then(res => {
if (res.status == 200) {
this.tableData = res.result;
let len = res.result.length;
let times = this.testPaper.times ? this.testPaper.times : 0;
if (times != 0 && len >= times) {
//考试次数限制
this.tipText = '已达到允许考试次数上限';
}
} else {
this.$refs.messager.show({message:'加载考试记录失败',type:'error'});
}
})
},
toShowAnswer(item){
uni.navigateTo({
url:`/pages/exam/answer?id=${item.id}&showAnalysis=${this.testPaper.showAnalysis}&showAnswer=${this.testPaper.showAnswer}`
})
},
formatSeconds(time) {
return formatSeconds(time);
},
startTest() {
this.curTestAnswer = {};
this.aloneExamAnswerId='';
this.curIndex=0;
// 先禁用,防止重复提交
if (this.testPaper.entranceTime && this.testPaper.entranceTime !== '') {
let time = new Date();
let entranceTime = new Date(this.testPaper.entranceTime);
if (time < entranceTime) {
uni.showLoading({
title:"考试还未开始,请耐心等候!",
duration:2000
});
return;
}
}
this.startButton = true;
this.remainingTime = this.testPaper.testDuration * 60;
//提取考试试卷内容
let startParams = {
exam: this.examId,
task: this.taskId,
lastId: this.lastId
}
let $this = this;
apiTestPaper.getTestStart(startParams).then(rs => {
if (rs.status == 200) {
//处理试卷内容
let paperQuestion = JSON.parse(rs.result.paper);
//以下是去掉分页符
let tempPaper = [];
paperQuestion.forEach(qitem=>{
qitem.userAnswer='';
if(qitem.type<900){
if(qitem.type==2){
qitem.userAnswer=[];
}
tempPaper.push(qitem);
}
});
this.paper=tempPaper;
this.total=this.paper.length;//计算试题总数
this.arrangeQuestion();
this.curItem = this.paper[this.curIndex];
//if(this.curItem.type==3){
//this.curItem.answer=this.curItem.answer=='true'? true:false;
//}
console.log(this.curItem,'this.curItem 1');
//进入考试阶段
this.testStatus = 2;
this.startReckon();
//60秒后执行保存处理,这里和pc端不一致不做延迟直接保存考试信息
//window.setTimeout(function() {
//$this.saveUserTest();
//}, 10000);
this.saveUserTest()
} else {
this.startButton = false;
uni.showLoading({
title:"加载试卷内容失败",
duration:2000
});
}
});
},
reStartTest(item){
//直接提取
this.curTestAnswer = {};
this.aloneExamAnswerId='';
this.curIndex=0;
apiTestPaper.getAnswerDetail(item.id).then(res => {
if (res.status === 200) {
this.aloneExamAnswerId=res.result.id;
this.curTestAnswer=res.result;
let answerJson = JSON.parse(res.result.answerJson);
let paperJson = JSON.parse(res.result.paperJson);
if(paperJson.length==0){
this.$refs.messager.show({message:'无试卷内容',type:'error'});
return;
}
//console.log(answerJson,'answerJson');
//console.log(paperJson,'paperJson');
let paperQuestions=[];//不包含分页符的试题数组
//设置答案
paperJson.forEach((qitem , index) => {
if(qitem.type<900){
let avalue=answerJson[qitem.id];
//console.log(avalue,'avalue')
if(qitem.type==3){
//qitem.answer=qitem.answer=='true'? true:false;
}
if(avalue){
if(qitem.type==1){ //单选
qitem.userAnswer=avalue;
//console.log(qitem,avalue,'单选qitem')
}else if(qitem.type==2){ //多选
qitem.userAnswer=avalue.split(',');
}else if(qitem.type==3){ //判断
//qitem.userAnswer=avalue=='true'? true:false;
qitem.userAnswer=avalue;
}
}
paperQuestions.push(qitem);
}
});
//设置选中状态及对于错
// paperQuestions.forEach((item, index) => {
// });
this.total=paperQuestions.length;
this.paper = paperQuestions;
this.curItem = this.paper[this.curIndex];
//console.log(this.curItem,'this.curItem');
if(res.result.useSecond){
this.remainingTime=this.testPaper.testDuration*60-res.result.useSecond;
}
this.testStatus = 2;
this.startReckon();
} else {
this.$refs.messager.show({message:res.message,type:'error'});
}
})
},
clearPagerJson(){
let paperJson = [];
this.paper.forEach((item,index) => {
let option = {
id:item.id,
optionList:[]
};
item.optionList.forEach(it=>{
option.optionList.push({id:it.id})
})
paperJson.push(option);
})
return JSON.stringify(paperJson);
},
saveUserTest() { //保存用户的考试在点击开始考试1分钟后执行
if (this.handleSaveTest != null) {
window.clearTimeout(this.handleSaveTest);
}
if (this.testStatus != 2) {
//只有在第二阶段才会保存
return;
}
let that = this;
let data = {};
data.testId = this.testPaper.id;
data.testName = this.testPaper.testName;
data.testDuration = this.testPaper.testDuration;
data.useSecond = 10; //用时秒
data.arrange = this.testPaper.arrange;
data.passLine = this.testPaper.passLine;
data.ucode = this.userInfo.userNo;
data.paperJson = this.clearPagerJson();
data.answerJson = this.getAnswer();
//计算总分
let total = 0;
this.paper.forEach(item => {
total += item.defaultScore;
});
data.totalScore = total;
//计算出当前的成绩
data.realScore = this.countScore();
apiTestPaper.start(data).then((res) => {
if (res.status == 200) {
that.aloneExamAnswerId = res.result.id;
that.curTestAnswer = res.result; //
//that.startUpdateAnswer();
} else {
uni.showLoading({
title:res.message,
duration:2000
});
}
})
},
countScore() {
let total = 0;
this.paper.forEach(item => {
if (item.type == 3) {
//console.log(item,item.answer,item.userAnswer,'item');
if (item.answer=='true' && item.userAnswer=='true') {
total += item.defaultScore;
}else if(item.answer=='false' && item.userAnswer=='false'){
total+=item.defaultScore;
}
}else if (item.type == 1) {
//console.log(item,item.defaultScore,'item.defaultScore')
item.optionList.forEach(opt => {
if (opt.id == item.userAnswer && opt.isAnswer) {
total += item.defaultScore;
}
})
//console.log(total,'total')
}else if (item.type == 2) {
let tempAnswer = [];
item.optionList.forEach(opt => {
if (opt.isAnswer) {
tempAnswer.push(opt.id);
}
})
tempAnswer.sort(function(n1, n2) {
return n1 > n2 ? -1 : 1;
});
if (item.userAnswer) {
item.userAnswer.sort(function(n1, n2) {
return n1 > n2 ? -1 : 1;
});
//console.log()
let str1 = item.userAnswer.join();
let str2 = tempAnswer.join();
//console.log(str1,str2,'aaa');
if (str1 == str2) {
total += item.defaultScore;
}
}
}
});
return total;
},
// 提交考卷
submit: function() {
// if(!this.aloneExamAnswerId){
// this.$refs.messager.show({message:'请先答题再提交',type:'error'});
// return;
// }
let that = this;
//检查答案,是否有,如果没有就不能提交
//试卷内容不传试题只是传id索引及目录
that.submitButton = true;
let data = {};
data.id = this.aloneExamAnswerId;
data.testId = this.testPaper.id;
data.testName = this.testPaper.testName;
data.testDuration = this.testPaper.testDuration;
data.arrange = this.testPaper.arrange;
data.passLine = this.testPaper.passLine;
data.scoreType=this.testPaper.scoringType;
data.ucode = this.userInfo.userNo;
//计算总分
let total = 0;
this.paper.forEach(item => {
total += item.defaultScore;
});
data.totalScore=total;
data.answerJson = this.getAnswer();
data.realScore=this.countScore();
if(!this.aloneExamAnswerId){
data.paperJson = this.clearPagerJson();
}
data.useSecond=this.testPaper.testDuration*60-this.remainingTime;
//计算百分制显示
data.score=data.realScore*100/data.totalScore;
this.thisTrue = false
apiTestPaper.submit(data).then((res) => {
this.thisTrue = true
if (res.status == 200) {
that.testStatus = 3;
this.score = data.score;
this.submitButton = false;
} else {
this.submitButton = false;
this.$refs.messager.show({message:res.message,type:'error'});
}
})
},
closeTest(){
uni.redirectTo({
url:'/pages/exam/exam?id='+this.examId
})
},
//自动提交答案
updateAnswer: function() {
if(!this.aloneExamAnswerId){
this.saveUserTest();
return;
}
let data = {};
data.id = this.aloneExamAnswerId;
data.json = this.getAnswer();
data.second=this.testPaper.testDuration*60-this.remainingTime;
data.score=this.countScore();
apiTestPaper.updateAnswer(data).then((res) => {
if (res.status != 200) {
console.log('自动记录答卷失败:'+res.message,res.error);
}
})
},
getAnswer: function() {
let answer = {};
if (this.paper.length > 0) {
this.paper.forEach(item => {
let judgeUserAnswer = "";
if (String(item.userAnswer) && item.userAnswer != null) {
judgeUserAnswer = item.userAnswer + "";
}
answer[item.id] = judgeUserAnswer;
});
}
return JSON.stringify(answer);
},
// 试题排序
arrangeQuestion() {
if (this.testPaper.arrange) {
if (this.testPaper.arrange == 1 || this.testPaper.arrange == 3) {
this.randArray(this.paper, this.paper.length)
}
if (this.testPaper.arrange == 2 || this.testPaper.arrange == 3) {
this.paper.forEach((item) => {
if (item.optionList && item.optionList.length > 0) {
this.randArray(item.optionList, item.optionList.length)
}
})
}
}
},
//数组乱序
randArray(m, len) {
m.sort(function() {
return Math.random() - 0.5
})
return m.slice(0, len)
},
//开始计时
startReckon: function() {
let that = this;
that.reckonTimeer = setInterval(function() {
if (that.remainingTime <= 0) {
that.stopReckon();
//自动提交,应该先提示,然后再自动提交
uni.showToast({
title:'时间已到自动提交'
})
that.submit();
} else {
that.remainingTime--;
}
}, 1000)
},
// 停止计时
stopReckon: function() {
window.clearInterval(this.reckonTimeer)
},
chooseOption(opt, is) {
let curQuestion=this.curItem;
//console.log(curQuestion,'选择前');
//curQuestion.userAnswer='';
if (curQuestion.type == 1) {
curQuestion.userAnswer=opt.id;
} else if (curQuestion.type == 2) {
if (!curQuestion.userAnswer){
curQuestion.userAnswer=[];
}
//如果存在就删除,如果不存在就添加
let hasIndex=-1;
curQuestion.userAnswer.some((item, index) => {
if (item == opt.id) {
hasIndex=index;
return true;
}else{
return false;
}
})
if(hasIndex>-1){
curQuestion.userAnswer.splice(hasIndex,1);
}else{
curQuestion.userAnswer.push(opt.id);
}
} else if (curQuestion.type == 3) {
curQuestion.userAnswer=is? 'true':'false';
//opt.checked = is;
//this.curItem.userAnswer = String(is);
}
//console.log(this.curItem,'选择后');
this.$forceUpdate();
//console.timeEnd(opt.id);
},
prevSub() {
if (this.curIndex == 0) {
return;
}
this.curIndex--;
if (this.curIndex > -1) {
this.curItem = this.paper[this.curIndex];
}
},
nextSub() {
//console.log(this.curItem,'this.curItem');
if (this.curIndex >= (this.total - 1)) {
return;
}
if(this.curItem.userAnswer=='undefined' || this.curItem.userAnswer==''){
return;
}
if(this.curItem.type==3){
//console.log(this.curItem.userAnswer,'判断题的值');
if(this.curItem.userAnswer=='true' || this.curItem.userAnswer=='false'){
this.curIndex++;
this.curItem = this.paper[this.curIndex];
//console.log(this.curItem,'切换后2');
this.updateAnswer();
}
}else{
//检查当前的是否已选答案
if(this.curItem.userAnswer){
this.curIndex++;
this.curItem = this.paper[this.curIndex];
//console.log(this.curItem,'切换后2');
this.updateAnswer();
}
}
//console.log(this.curItem,'this.curItem');
},
// 人工提交
manualSubmit() {
let that = this;
uni.showModal({
title: '提示',
content: '您确定要提交试卷吗?',
success(res) {
if (res.confirm) {
that.stopReckon();
that.submit();
}
}
});
},
getQuestionType(type) {
let name = '';
switch (type) {
case 1:
name = '单选';
break;
case 2:
name = '多选';
break;
case 3:
name = '判断';
break;
}
return name;
}
}
}
</script>
<style lang="scss" scoped>
.epage{
background-color: #FFFFFF;
padding-bottom: 50px;
}
.history{
.history-item{
line-height: 80upx;
border-bottom: 2upx solid #c5c5c5;
.history-row{
display: flex;
justify-content: space-between;
font-size: 12px;
}
}
}
.sta-can {
text-align: center;
color: red;
margin-top: 20px;
}
.pager-title {
height: 40px;
display: flex;
justify-content: space-between;
line-height: 40px;
padding: 0 20upx;
.title-name {
margin-left: 20upx;
font-weight: bold;
}
.titlt-btn {
display: flex;
.sub {
height: 20px;
border-radius: 40px;
margin-top: 10px;
margin-left: 20upx;
}
}
}
.score-box {
height: 40px;
text-align: center;
margin: 20px;
}
.sta-btn {
margin: 20upx 50upx;
}
.redText {
color: #ff0000;
}
.footer {
position: fixed;
bottom: 0px;
width: 100%;
height: 80upx;
line-height: 80upx;
border-top: 1px solid #d9d9d9;
background-color: #FFFFFF;
}
.column {
color: #838383;
font-size: 40upx;
padding: 10px 20px;
}
.qitem {
padding: 10px 20px;
.qitem-info {
font-size: 1.2em;
padding: 10px 0px;
}
.qitem-opts {
padding: 5px 0px;
.qitem-opt {
display: flex;
margin-bottom: 10px;
padding: 20px 15px;
background-color: #FFFFFF;
border-radius: 6px;
}
.check {
color: #00aa00;
}
}
}
.bottom-btns {
padding: 10px 0px;
position: fixed;
width: 100%;
bottom: 0px;
.next {
width: 60%;
border-radius: 40px;
margin-bottom: 20px;
}
// display: flex;
// justify-content: space-around;
}
.qimg{
width:100%;
.qimg-fit{
width:100%;
object-fit:scale-down
}
}
</style>