在线考试和考试结果

pg_adapter
dshclm 11 months ago
parent 06d162d5a4
commit 1552e20259

@ -46,9 +46,10 @@ public class PaperController extends BaseController {
@ApiOperation("去考试")
@GetMapping("/toPaper/{examId}")
public String toPaper(@PathVariable("examId") String examId, ModelMap mmap) {
@GetMapping("/toPaper/{examId}/{paperId}")
public String toPaper(@PathVariable("examId") String examId, @PathVariable String paperId,ModelMap mmap ) {
mmap.put("examId", examId);
mmap.put("paperId", paperId);
return prefix + "/paper";
}
@ -110,7 +111,6 @@ public class PaperController extends BaseController {
}
@ApiOperation("详情")
@GetMapping("/paperResult/{paperId}")
@ResponseBody

@ -112,13 +112,13 @@ public class PaperManager {
/**
*
*
*
* @param examId
* @param userId
* @return java.lang.String
*/
@Transactional(rollbackFor = Exception.class)
public String createPaper(String examId,Long userId) {
// 校验是否有正在考试的试卷
@ -360,13 +360,13 @@ public class PaperManager {
}
/**
*
*
*
* @param paperId
* @param quId
* @return com.ruoyi.system.domain.paper.dto.ext.PaperQuDetailDTO
*/
public PaperQuDetailDTO findQuDetail(String paperId, String quId) {
// 问题
ElQu qu = quService.getById(quId);
@ -431,12 +431,12 @@ public class PaperManager {
}
/**
*
*
*
* @param paperId
* @return boolean
*/
@Transactional(rollbackFor = Exception.class)
public boolean submitExam(String paperId) {
//获取试卷信息

@ -34,11 +34,21 @@
<el-col :span="24" style="margin-bottom: 20px">
<el-alert
title="点击`开始考试`后将自动进入考试,请诚信考试!"
type="error"
style="margin-bottom: 10px"
title="点击`开始考试`后将自动进入考试,请诚信考试!"
type="error"
style="margin-bottom: 10px"
>
</el-alert>
<span @click="toExam()">
<el-alert
v-if="breakShow"
:closable="false"
title="您有正在进行的考试,离线太久考试将被作废哦,点击此处可继续考试!"
type="error"
style="margin-bottom: 10px; cursor: pointer;"
>
</el-alert>
</span>
<el-card class="pre-exam">
<div><strong>考试名称:</strong>[[${ exam.title }]]</div>
@ -54,7 +64,7 @@
<el-col :span="24">
<el-button :loading="loading" type="primary" icon="el-icon-caret-right" onclick="pass()">
<el-button :loading="loading" :disabled="breakShow === true" type="primary" icon="el-icon-caret-right" @click="fetchData()">
开始考试
</el-button>
@ -72,7 +82,7 @@
<th:block th:include="include :: element-js" />
<th:block th:include="include :: datetimepicker-js" />
<script type="text/javascript">
var prefix = ctx + "system/paper";
var app = new Vue({
el: '#app',
data:{
@ -87,39 +97,90 @@ var app = new Vue({
]
},
loading: false
loading: false,
breakShow: false,
},
created() {
// this.postForm.examId = this.$route.params.examId
// this.fetchData()
console.log('[[${exam.id}]]')
mounted(){
this.check()
},
methods: {
fetchData() {
fetchDetail(this.postForm.examId).then(response => {
this.detailData = response.data
// 弹出层全屏
openFull(title, url, width, height) {
// 如果是移动端,就使用自适应大小弹窗
if ($.common.isMobile()) {
width = 'auto';
height = 'auto';
}
if ($.common.isEmpty(title)) {
title = false;
}
if ($.common.isEmpty(url)) {
url = "/404.html";
}
if ($.common.isEmpty(width)) {
width = 800;
}
if ($.common.isEmpty(height)) {
height = ($(window).height() - 50);
}
var index = top.layer.open({
type: 2,
area: [width + 'px', height + 'px'],
fix: false,
//不固定
maxmin: true,
shade: 0.3,
title: title,
content: url,
// 弹层外区域关闭
shadeClose: true,
yes: function(index, layero) {
var iframeWin = layero.find('iframe')[0];
iframeWin.contentWindow.submitHandler(index, layero);
},
cancel: function(index) {
return true;
},
success: function () {
$(':focus').blur();
}
});
top.layer.full(index);
},
// 继续考试
toExam() {
var url = 'system/paper/toPaper/[[${exam.id}]]/' + this.breakId
this.openFull("考试", url);
},
// 检查进行中的考试
check() {
let that = this
axios.get('/system/paper/checkProcess').then(res => {
if (res.data.data && res.data.data.id) {
that.breakShow = true
that.breakId = res.data.data.id
}
})
},
handleCreate() {
fetchData() {
const that = this
this.loading = true
createPaper(this.postForm).then(response => {
console.log(response)
if (response.code === 0) {
setTimeout(function () {
axios.get(prefix+'/create/[[${exam.id}]]')
.then((res)=>{
if (res.data.code === 0) {
setTimeout(function () {
this.loading = false
that.dialogVisible = false
var url = 'system/paper/toPaper/[[${exam.id}]]/'+res.data.msg
that.openFull("考试", url);
}, 1000)
}else{
this.$message.error(res.data.msg)
this.loading = false
that.dialogVisible = false
that.$router.push({name: 'StartExam', params: {id: response.data.id}})
}, 1000)
}
}).catch(() => {
this.loading = false
})
},
}
})
}
}
})

@ -1,10 +1,459 @@
<!DOCTYPE html>
<html lang="en">
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>Title</title>
<th:block th:include="include :: header('新增题库')" />
<th:block th:include="include :: element-css" />
</head>
<body>
<style>
.qu-content div{
line-height: 30px;
width: 100%;
}
.el-checkbox-group label,.el-radio-group label{
width: 100%;
}
.content-h{
height: calc(100vh - 110px);
overflow-y: auto;
}
.card-title{
background: #eee;
line-height: 35px;
text-align: center;
font-size: 14px;
}
.card-line{
padding-left: 10px
}
.card-line span {
cursor: pointer;
margin: 2px;
}
.el-radio, .el-checkbox{
padding: 9px 20px 9px 10px;
border-radius: 4px;
border: 1px solid #dcdfe6;
margin-bottom: 10px;
width: 100%;
}
.is-checked{
border: #409eff 1px solid;
}
.el-radio img, .el-checkbox img{
max-width: 200px;
max-height: 200px;
border: #dcdfe6 1px dotted;
}
.el-checkbox__inner {
display: none;
}
.el-radio__inner{
display: none;
}
.el-checkbox__label{
line-height: 30px;
}
.el-radio__label{
line-height: 30px;
}
.ibox-content{
height: 99%;
}
</style>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content" id="app">
<div class="app-container">
<el-row :gutter="24">
<el-col :span="24">
<el-card style="margin-bottom: 10px">
距离考试结束还有:
<span style="color: #ff0000; font-weight: 700">{{ min }}分钟{{ sec }}秒</span>
<el-button :loading="loading" style="float: right; margin-top: -10px" type="primary" icon="el-icon-plus" @click="handHandExam()">
{{ handleText }}
</el-button>
</el-card>
</el-col>
<el-col :span="5" :xs="24" style="margin-bottom: 10px">
<el-card class="content-h">
<p class="card-title">答题卡</p>
<el-row :gutter="24" class="card-line" style="padding-left: 10px">
<el-tag type="info">未作答</el-tag>
<el-tag type="success">已作答</el-tag>
</el-row>
<div v-if="paperData.radioList!==undefined && paperData.radioList.length > 0">
<p class="card-title">单选题</p>
<el-row :gutter="24" class="card-line">
<el-tag v-for="item in paperData.radioList" :type="cardItemClass(item.answered, item.quId)" @click="handSave(item)"> {{ item.sort+1 }}</el-tag>
</el-row>
</div>
<div v-if="paperData.multiList!==undefined && paperData.multiList.length > 0">
<p class="card-title">多选题</p>
<el-row :gutter="24" class="card-line">
<el-tag v-for="item in paperData.multiList" :type="cardItemClass(item.answered, item.quId)" @click="handSave(item)">{{ item.sort+1 }}</el-tag>
</el-row>
</div>
<div v-if="paperData.judgeList!==undefined && paperData.judgeList.length > 0">
<p class="card-title">判断题</p>
<el-row :gutter="24" class="card-line">
<el-tag v-for="item in paperData.judgeList" :type="cardItemClass(item.answered, item.quId)" @click="handSave(item)">{{ item.sort+1 }}</el-tag>
</el-row>
</div>
</el-card>
</el-col>
<el-col :span="19" :xs="24">
<el-card class="qu-content content-h">
<p v-if="quData.content">{{ quData.sort + 1 }}.{{ quData.content }}</p>
<p v-if="quData.image!=null && quData.image!=''">
<el-image :src="quData.image" style="max-width:100%;" />
</p>
<div v-if="quData.quType === 1 || quData.quType===3">
<el-radio-group v-model="radioValue">
<el-radio v-for="item in quData.answerList" :label="item.id">{{ item.abc }}.{{ item.content }}
<div v-if="item.image!=null && item.image!=''" style="clear: both">
<el-image :src="item.image" style="max-width:100%;" />
</div>
</el-radio>
</el-radio-group>
</div>
<div v-if="quData.quType === 2">
<el-checkbox-group v-model="multiValue">
<el-checkbox v-for="item in quData.answerList" :key="item.id" :label="item.id">{{ item.abc }}.{{ item.content }}
<div v-if="item.image!=null && item.image!=''" style="clear: both">
<el-image :src="item.image" style="max-width:100%;" />
</div>
</el-checkbox>
</el-checkbox-group>
</div>
<div style="margin-top: 20px">
<el-button v-if="showPrevious" type="primary" icon="el-icon-back" @click="handPrevious()">
上一题
</el-button>
<el-button v-if="showNext" type="warning" icon="el-icon-right" @click="handNext()">
下一题
</el-button>
</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: element-js" />
<script type="text/javascript">
var prefix = ctx + "system/paper";
var app = new Vue({
el: '#app',
data:{
// 全屏/不全屏
isFullscreen: false,
showPrevious: false,
showNext: true,
loading: false,
handleText: '交卷',
pageLoading: false,
// 试卷ID
paperId: '',
//
examId:'',
// 当前答题卡
cardItem: {},
allItem: [],
// 当前题目内容
quData: {
answerList: []
},
// 试卷信息
paperData: {
leftSeconds: 99999,
radioList: [],
multiList: [],
judgeList: []
},
// 单选选定值
radioValue: '',
// 多选选定值
multiValue: [],
// 已答ID
answeredIds: [],
min:'',
sec:''
},
mounted() {
this.paperId = '[[${paperId}]]'
this.examId = '[[${examId}]]'
this.fetchData()
},
watch:{
'paperData.leftSeconds': {
handler(newVal) {
// this.paperData.leftSeconds = newVal
this.countdown()
}
}
},
methods: {
countdown() {
// 清除之前的定时器,确保只有一个定时器在运行
if (this.timer) {
clearInterval(this.timer)
}
this.timer = setInterval(() => {
if (this.paperData.leftSeconds < 0) {
this.doHandler()
clearInterval(this.timer) // 清除定时器
return
}
this.min = parseInt(this.paperData.leftSeconds / 60).toString().padStart(2, '0')
this.sec = parseInt(this.paperData.leftSeconds % 60).toString().padStart(2, '0')
this.paperData.leftSeconds -= 1
console.log(this.min, this.sec)
}, 1000)
},
// 答题卡样式
cardItemClass(answered, quId) {
if (quId === this.cardItem.quId) {
return 'warning'
}
if (answered) {
return 'success'
}
if (!answered) {
return 'info'
}
},
/**
* 统计有多少题没答的
* @returns {number}
*/
countNotAnswered() {
let notAnswered = 0
this.paperData.radioList.forEach(function(item) {
if (!item.answered) {
notAnswered += 1
}
})
this.paperData.multiList.forEach(function(item) {
if (!item.answered) {
notAnswered += 1
}
})
this.paperData.judgeList.forEach(function(item) {
if (!item.answered) {
notAnswered += 1
}
})
return notAnswered
},
/**
* 下一题
*/
handNext() {
const index = this.cardItem.sort + 1
this.handSave(this.allItem[index])
},
/**
* 上一题
*/
handPrevious() {
const index = this.cardItem.sort - 1
this.handSave(this.allItem[index])
},
doHandler() {
this.handleText = '正在交卷,请等待...'
this.loading = true
let that = this
axios.get(prefix + '/submitExam/' + this.paperId).then(() => {
that.$message({
message: '试卷提交成功,即将进入试卷详情!',
type: 'success'
})
$.modal.close()
var url = 'system/paper/toView/' + that.paperId
$.modal.openTab("考试结果", url);
})
},
// 交卷操作
handHandExam() {
const that = this
// 交卷保存答案
this.handSave(this.cardItem, function() {
const notAnswered = that.countNotAnswered()
let msg = '确认要交卷吗?'
if (notAnswered > 0) {
msg = '您还有' + notAnswered + '题未作答,确认要交卷吗?'
}
that.$confirm(msg, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
that.doHandler()
}).catch(() => {
that.$message({
type: 'info',
message: '交卷已取消,您可以继续作答!'
})
})
})
},
// 保存答案
handSave(item, callback) {
if (item.id === this.allItem[0].id) {
this.showPrevious = false
} else {
this.showPrevious = true
}
// 最后一个索引
const last = this.allItem.length - 1
if (item.id === this.allItem[last].id) {
this.showNext = false
} else {
this.showNext = true
}
const answers = this.multiValue
if (this.radioValue !== '') {
answers.push(this.radioValue)
}
const params = { paperId: this.paperId, quId: this.cardItem.quId, answers: answers, answer: '' }
let that = this
axios.post(prefix + '/fillAnswer',params).then(() => {
// 必须选择一个值
if (answers.length > 0) {
// 加入已答列表
that.cardItem.answered = true
}
// 最后一个动作,交卷
if (callback) {
callback()
}
// 查找详情
that.fetchQuData(item)
})
},
// 试卷详情
fetchQuData(item) {
// 获得详情
this.cardItem = item
// 查找下个详情
let that = this
const params = { paperId: this.paperId, quId: item.quId }
axios.post(prefix+'/quDetail',params).then(response => {
that.quData = response.data.data
that.radioValue = ''
that.multiValue = []
let _that = that
// 填充该题目的答案
that.quData.answerList.forEach((item) => {
if ((_that.quData.quType === 1 || _that.quData.quType === 3) && item.checked) {
_that.radioValue = item.id
}
if (_that.quData.quType === 2 && item.checked) {
_that.multiValue.push(item.id)
}
})
})
},
// 试卷详情
fetchData() {
let _that = this
axios.get(prefix+'/detail/'+ this.paperId)
.then((res)=>{
// 试卷内容
_that.paperData = res.data.data
// 获得第一题内容
if (_that.paperData.radioList && _that.paperData.radioList.length>0) {
_that.cardItem = _that.paperData.radioList[0]
} else if (_that.paperData.multiList && _that.paperData.multiList.length>0) {
_that.cardItem = _that.paperData.multiList[0]
} else if (_that.paperData.judgeList && _that.paperData.judgeList.length>0) {
_that.cardItem = _that.paperData.judgeList[0]
}
const that = _that
_that.paperData.radioList.forEach(function(item) {
that.allItem.push(item)
})
_that.paperData.multiList.forEach(function(item) {
that.allItem.push(item)
})
_that.paperData.judgeList.forEach(function(item) {
that.allItem.push(item)
})
// 当前选定
_that.fetchQuData(_that.cardItem)
})
}
}
})
</script>
</body>
</html>
</html>

@ -0,0 +1,238 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('考试')" />
<th:block th:include="include :: select2-css" />
<th:block th:include="include :: datetimepicker-css" />
<th:block th:include="include :: element-css" />
</head>
<style>
.qu-content{
border-bottom: #eee 1px solid;
padding-bottom: 10px;
}
.qu-content div{
line-height: 30px;
}
.el-checkbox-group label,.el-radio-group label{
width: 100%;
}
.card-title{
background: #eee;
line-height: 35px;
text-align: center;
font-size: 14px;
}
.card-line{
padding-left: 10px
}
.card-line span {
cursor: pointer;
margin: 2px;
}
</style>
<body>
<div class="main-content" id="app">
<div class="app-container">
<h2 class="text-center">{{ paperData.title }}</h2>
<p class="text-center" style="color: #666">{{ paperData.createTime }}</p>
<el-row :gutter="24" style="margin-top: 50px">
<el-col :span="8" class="text-center">
考生姓名:{{ paperData.userId }}
</el-col>
<el-col :span="8" class="text-center">
考试用时:{{ paperData.userTime }}分钟
</el-col>
<el-col :span="8" class="text-center">
考试得分:{{ paperData.userScore }}
</el-col>
</el-row>
<el-card style="margin-top: 20px">
<div v-for="item in paperData.quList" :key="item.id" class="qu-content">
<p>{{ item.sort + 1 }}.{{ item.content }}(得分:{{ item.actualScore }}</p>
<p v-if="item.image!=null && item.image!=''">
<el-image :src="item.image" style="max-width:100%;" ></el-image>
</p>
<div v-if="item.quType === 1 || item.quType===3">
<el-radio-group v-model="radioValues[item.id]">
<el-radio v-for="an in item.answerList" :label="an.id">
{{ an.abc }}.{{ an.content }}
<div v-if="an.image!=null && an.image!=''" style="clear: both">
<el-image :src="an.image" style="max-width:100%;" ></el-image>
</div>
</el-radio>
</el-radio-group>
<el-row :gutter="24">
<el-col :span="12" style="color: #24da70">
正确答案:{{ radioRights[item.id] }}
</el-col>
<el-col v-if="!item.answered" :span="12" style="text-align: right; color: #ff0000;">
答题结果:未答
</el-col>
<el-col v-if="item.answered && !item.isRight" :span="12" style="text-align: right; color: #ff0000;">
答题结果:{{ myRadio[item.id] }}
</el-col>
<el-col v-if="item.answered && item.isRight" :span="12" style="text-align: right; color: #24da70;">
答题结果:{{ myRadio[item.id] }}
</el-col>
</el-row>
</div>
<div v-if="item.quType === 4">
<el-row :gutter="24">
<el-col :span="12">
我的回答:{{ item.answer }}
</el-col>
</el-row>
</div>
<div v-if="item.quType === 2">
<el-checkbox-group v-model="multiValues[item.id]">
<el-checkbox v-for="an in item.answerList" :key="an.id" :label="an.id">{{ an.abc }}.{{ an.content }}
<div v-if="an.image!=null && an.image!=''" style="clear: both">
<el-image :src="an.image" style="max-width:100%;" ></el-image>
</div>
</el-checkbox>
</el-checkbox-group>
<el-row :gutter="24">
<el-col :span="12" style="color: #24da70">
正确答案:{{ multiRights[item.id].join(',') }}
</el-col>
<el-col v-if="!item.answered" :span="12" style="text-align: right; color: #ff0000;">
答题结果:未答
</el-col>
<el-col v-if="item.answered && !item.isRight" :span="12" style="text-align: right; color: #ff0000;">
答题结果:{{ myMulti[item.id].join(',') }}
</el-col>
<el-col v-if="item.answered && item.isRight" :span="12" style="text-align: right; color: #24da70;">
答题结果:{{ myMulti[item.id].join(',') }}
</el-col>
</el-row>
</div>
</div>
</el-card>
</div>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: select2-js" />
<th:block th:include="include :: element-js" />
<th:block th:include="include :: datetimepicker-js" />
<script type="text/javascript">
var prefix = ctx + "system/paper";
var app = new Vue({
el: '#app',
data:{
// 试卷ID
paperId: '',
paperData: {
quList: []
},
radioValues: {},
multiValues: {},
radioRights: {},
multiRights: {},
myRadio: {},
myMulti: {}
},
created() {
// const id = this.$route.params.id
// if (typeof id !== 'undefined') {
this.paperId = '[[${paperId}]]'
this.fetchData()
// }
},
methods: {
fetchData() {
let that = this
axios.get('/system/paper/paperResult/'+ this.paperId).then(response => {
// 试卷内容
that.paperData = response.data.data
// 填充该题目的答案
that.paperData.quList.forEach((item) => {
let radioValue = ''
let radioRight = ''
let myRadio = ''
const multiValue = []
const multiRight = []
const myMulti = []
item.answerList.forEach((an) => {
// 用户选定的
if (an.checked) {
if (item.quType === 1 || item.quType === 3) {
radioValue = an.id
myRadio = an.abc
} else {
multiValue.push(an.id)
myMulti.push(an.abc)
}
}
// 正确答案
if (an.isRight) {
if (item.quType === 1 || item.quType === 3) {
radioRight = an.abc
} else {
multiRight.push(an.abc)
}
}
})
that.multiValues[item.id] = multiValue
that.radioValues[item.id] = radioValue
that.radioRights[item.id] = radioRight
that.multiRights[item.id] = multiRight
that.myRadio[item.id] = myRadio
that.myMulti[item.id] = myMulti
})
console.log(that.multiValues)
console.log(that.radioValues)
window.parent.$.modal.closeLoading();
})
}
}
})
</script>
</body>
</html>
Loading…
Cancel
Save