提交 01c48e8f authored 作者: 王鹏飞's avatar 王鹏飞

chore: 修改课程考试页面

上级 523bbbb5
......@@ -284,4 +284,126 @@ export default class PlayerAction extends BaseACTION {
getLiveList () { return Player.getLiveList().then(res => res) }
/* 获取云课堂 url */
getCloudUrl () { return Player.getCloudUrl().then(res => res) }
/* 获取考卷信息 */
getExamInfo (cid, sid) {
return Player.getExamInfo(cid, sid).then(_res => {
const exam = {}
exam.id = _res.id
exam.title = _res.title
exam.score = {}
exam.radioList = _res.examination.radioList
for (let i = 0; i < exam.radioList.length; i++) {
exam.radioList[i].user_answer = ''
exam.radioList[i].right_answer = ''
exam.radioList[i].get_score = -1
}
exam.checkboxList = _res.examination.checkboxList
for (let i = 0; i < exam.checkboxList.length; i++) {
exam.checkboxList[i].user_answer = []
exam.checkboxList[i].right_answer = []
exam.checkboxList[i].get_score = -1
}
exam.shortAnswerList = _res.examination.shortAnswerList
for (let i = 0; i < exam.shortAnswerList.length; i++) {
exam.shortAnswerList[i].user_answer = ''
exam.shortAnswerList[i].get_score = -1
exam.shortAnswerList[i].attachments = []
exam.shortAnswerList[i].upload = {
type: 'upload-form',
label: '附件上传:',
model: 'attachments',
action: webConf.apiBaseURL + '/util/upload-file',
data: {
special: 'exam'
},
attrs: {
multiple: true,
headers: {
tenant: 'sofia'
}
},
html: `
<div style="color: #72818c; font-size: 14px;">
<p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M。</p>
</div>
`
}
}
return exam
})
}
/* 获取考卷结果 */
getExamAnswer (cid, sid, eid) {
return Player.getExamAnswer(cid, sid, eid).then(_res => {
if (_res.code) { return _res }
const exam = {}
let tmp = null
exam.id = _res.id
exam.title = _res.title
exam.type = _res.type
exam.score = _res.score
exam.isPublished = _res.is_published || ''
exam.submitted_time = _res.submitted_time
exam.radioList = _res.sheet.radioList
for (let i = 0; i < exam.radioList.length; i++) {
tmp = exam.radioList[i]
if (!tmp.user_answer) tmp.user_answer = ''
if (!tmp.right_answer) tmp.right_answer = ''
if (!tmp.get_score) tmp.get_score = -1
}
exam.checkboxList = _res.sheet.checkboxList
for (let i = 0; i < exam.checkboxList.length; i++) {
tmp = exam.checkboxList[i]
if (!tmp.user_answer || !tmp.user_answer.length) tmp.user_answer = []
if (!tmp.right_answer || !tmp.right_answer.length) tmp.right_answer = []
if (!tmp.get_score) tmp.get_score = -1
}
exam.shortAnswerList = _res.sheet.shortAnswerList
for (let i = 0; i < exam.shortAnswerList.length; i++) {
tmp = exam.shortAnswerList[i]
tmp.user_answer = Base64.decode(tmp.user_answer.replace(/ /gi, '+'))
if (!tmp.attachments || !tmp.attachments.length) tmp.attachments = []
tmp.upload = {
type: 'upload-form',
label: '附件上传:',
model: 'attachments',
action: webConf.apiBaseURL + '/util/upload-file',
data: {
special: 'exam'
},
attrs: {
multiple: true,
headers: {
tenant: 'sofia'
}
},
html: `
<div style="color: #72818c; font-size: 14px;">
<p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M。</p>
</div>
`
}
}
return exam
})
}
/* 获取考试状态 */
getExamStatus (cid, sid, eid) {
return Player.getExamStatus(cid, sid, eid).then(_res => {
/* 00: 考场未开放,不允许进入
10:考场开放,允许进入
20:开始答题
90:考试已结束 */
return _res
})
}
/* 提交考卷 */
submitExam (cid, sid, eid, obj) {
return Player.submitExam(cid, sid, eid, obj).then(_res => {
return _res
})
}
}
......@@ -100,9 +100,9 @@ export default class API {
if (data && data.code !== undefined) {
if (data.code !== 0) {
if (!/account\/get-user-info/gi.test(res.config.url)) {
Message({ type: 'error', message: data.msg })
data.msg && Message({ type: 'error', message: data.msg })
}
return null
return data
} else if (data.code === 0) {
return data.data
}
......
......@@ -101,4 +101,32 @@ export default class PlayerAPI extends BaseAPI {
* 跨域接口请求 - 直接获取云课堂设置
*/
getCloudUrl = (obj = {}) => this.get('https://node-server.ezijing.com/get/cloud-class', obj)
/**
* 获取考卷信息
* @param {[string]} course_id -> cid
* @param {[string]} semester_id -> sid
*/
getExamInfo = (cid, sid) => this.get(`/v2/education/${sid}/${cid}/examination`, {})
/**
* 获取考卷结果
* @param {[string]} course_id -> cid
* @param {[string]} semester_id -> sid
* @param {[string]} exam_id -> eid
*/
getExamAnswer = (cid, sid, eid) => this.get(`/v2/education/${sid}/${cid}/examination/${eid}/sheet`, {})
/**
* 获取考试状态
* @param {[string]} course_id -> cid
* @param {[string]} semester_id -> sid
* @param {[string]} exam_id -> eid
*/
getExamStatus = (cid, sid, eid) => this.get(`/v2/education/${sid}/${cid}/examination/${eid}/status`, {})
/**
* 提交考卷
* @param {[string]} course_id -> cid
* @param {[string]} semester_id -> sid
* @param {[string]} exam_id -> eid
* @param {[object]} obj -> 提交对象类
*/
submitExam = (cid, sid, eid, obj = {}) => this.post(`/v2/education/${sid}/${cid}/examination/${eid}/sheet`, obj, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
}
## 组件简介
| 字段值 | 说明 | 字段属性 | 默认值 |
| ------- | ------------------------- | ------- | ----- |
| `type` | 类型:`String`; 说明:组件类型名 | 自定义字段 | `upload-form` |
| `action` | 类型:`String`; 说明:上传请求接口path | 自定义字段 | `` |
| `deleteAction` | 类型:`String`; 说明:删除请求接口path | 自定义字段 | `` |
| `html` | 类型:`String`; 说明:上传说明,支持html | 自定义字段 | `` |
| `label` | 类型:`String`; 说明:组件左侧显示名称 | element-ui el-form-item对应字段 | `''` |
| `label-width` | 类型:`String`; 说明:组件左侧显示名称宽度(加单位),父级设置可以子级继承 | element-ui el-form-item对应字段 | `''` |
| `required` | 类型:`Boolean`; 说明:标识是否必填 | element-ui el-form-item对应字段 | `false` |
| `disabled` | 类型:`Boolean`; 说明:标识是否只读 | element-ui el-form-item对应字段 | `false` |
| `model` | 类型:`String`; 说明:表单提交name值和回显对照字段 | 自定义字段 | `''` |
| `placeholder` | 类型:`String`; 说明:组件input框中,默认提示文字 | element-ui el-input对应字段 | `''` |
| `attrs` | 类型:`Object`; 说明:定义标签上Data属性值 | element-ui对应字段 | `{}` |
| `rules` | 类型:`Array`; 说明:组件错误提示规则 | element-ui el-form-item对应字段 | `[]` |
### Demo Example:
``` js
return {
type: 'upload-form',
label: '姓名',
labeWidth: '160px',
required: true,
disabled: false,
model: 'uploadArrs',
action: '',
data: {
},
deleteAction: '',
deleteData: {
},
html: `
<div style="color: #72818c; font-size: 14px;">
<p style="margin: 0;">申请者需要将有效身份证件原件扫描或者拍照后提交。</p>
<p style="margin: 0;">请您提供有效身份证件的扫描件,身份证与台港澳居民大陆通行证应包括正反两面扫描件。</p>
<p style="margin: 0;">只上传一个文件,多份文件需合并到一个文件后打印出来检查无误后再上传。</p>
<p style="margin: 0;">上传文件仅限“jpg,jpeg,gif,png”格式,文件小于10MB。</p>
</div>
`,
attrs: {
multiple: false,
limit: 1
},
rules: [
{
required: true,
message: '请上传',
trigger: 'blur'
}
]
}
```
* 其他属性 [参考文档]([https://](https://element.eleme.cn/#/zh-CN/component/input))
import Upload from './src/uploadForm.vue'
/* istanbul ignore next */
Upload.install = function (Vue) {
Vue.component(Upload.name, Upload)
}
export default Upload
function getError (action, option, xhr) {
let msg
if (xhr.response) {
msg = `${xhr.response.error || xhr.response}`
} else if (xhr.responseText) {
msg = `${xhr.responseText}`
} else {
msg = `fail to post ${action} ${xhr.status}`
}
const err = new Error(msg)
err.status = xhr.status
err.method = 'post'
err.url = action
return err
}
function getBody (xhr) {
const text = xhr.responseText || xhr.response
if (!text) {
return text
}
try {
return JSON.parse(text)
} catch (e) {
return text
}
}
export function deleteFile (option) {
if (typeof XMLHttpRequest === 'undefined') {
return
}
// eslint-disable-next-line no-undef
const xhr = new XMLHttpRequest()
let action = option.action
xhr.onerror = function error (e) {
option.onError(e)
}
xhr.onload = function onload () {
if (xhr.status < 200 || xhr.status >= 300) {
return option.onError(getError(action, option, xhr))
}
option.onSuccess(getBody(xhr))
}
xhr.open('delete', action, true)
if (option.withCredentials && 'withCredentials' in xhr) {
xhr.withCredentials = true
}
const headers = option.headers || {}
for (let item in headers) {
if (headers.hasOwnProperty(item) && headers[item] !== null) {
xhr.setRequestHeader(item, headers[item])
}
}
xhr.send()
return xhr
}
export default function upload (option) {
if (typeof XMLHttpRequest === 'undefined') {
return
}
// eslint-disable-next-line no-undef
const xhr = new XMLHttpRequest()
const action = option.action
if (xhr.upload) {
xhr.upload.onprogress = function progress (e) {
if (e.total > 0) {
e.percent = e.loaded / e.total * 100
}
option.onProgress(e)
}
}
// eslint-disable-next-line no-undef
const formData = new FormData()
if (option.data) {
Object.keys(option.data).forEach(key => {
formData.append(key, option.data[key])
})
}
formData.append(option.filename, option.file, option.file.name)
xhr.onerror = function error (e) {
option.onError(e)
}
xhr.onload = function onload () {
if (xhr.status < 200 || xhr.status >= 300) {
return option.onError(getError(action, option, xhr))
}
option.onSuccess(getBody(xhr))
}
xhr.open('post', action, true)
if (option.withCredentials && 'withCredentials' in xhr) {
xhr.withCredentials = true
}
const headers = option.headers || {}
for (let item in headers) {
if (headers.hasOwnProperty(item) && headers[item] !== null) {
xhr.setRequestHeader(item, headers[item])
}
}
xhr.send(formData)
return xhr
}
<template>
<div class="upload-form">
<div class="u-babel">{{ item.label }}</div>
<el-upload style="display: inline-block;"
:action="item.action"
:data="item.data"
:before-upload="beforeUploadFile"
:on-success="onSuccessFile"
:with-credentials="true"
:show-file-list="false"
:disabled="(item.disabled || false) || !isUpload"
v-bind="item.attrs || {}"
>
<el-button type="primary" size="small" :disabled="!isUpload">点击上传</el-button>
<template v-if="formData[item.model] !== null && formData[item.model] !== '' && formData[item.model] !== undefined">
<div class="self-icon el-icon-circle-check" style="color: #237f00;"></div>
</template>
<div class="self-icon el-icon-circle-close" style="color: #b01c40;"></div>
</el-upload>
<div style="overflow: hidden; padding: 10px 0 0 0;">
<template v-if="filesArr.length">
<!-- 遍历显示文件 -->
<template v-for="(item, index) in filesArr">
<template v-if="/(jpeg)|(jpg)|(png)|(gif)/gi.test(item.url)">
<div v-bind:key="item.id" class="show-file">
<template v-if="!(item.disabled || false) && isUpload">
<div class="close" @click="deleteFiles(index)">X</div>
</template>
<el-avatar shape="square" :size="100" fit="contain" :src="item.url"></el-avatar>
<span class="title">{{ item.sso_file_name }}</span>
<div class="hover">
<a target="_blank" :href="item.url">下载</a>
</div>
</div>
</template>
<template v-else>
<div v-bind:key="item.id" class="show-file">
<template v-if="!(item.disabled || false) && isUpload">
<div class="close" @click="deleteFiles(index)">X</div>
</template>
<el-avatar shape="square" :size="100" fit="contain" :src="item.url"></el-avatar>
<span class="title">{{ item.sso_file_name }}</span>
<div class="hover">
<a target="_blank" :href="item.url">下载</a>
</div>
</div>
</template>
</template>
</template>
</div>
<div class='info' style="line-height: 1.5;" v-html="item.html"></div>
</div>
</template>
<script>
// import { deleteFile } from './ajax'
export default {
name: 'UploadForm',
componentName: 'UploadForm',
props: {
item: {
type: Object,
default () {
return {}
}
},
formData: {
type: Object,
default () {
return {}
}
},
isUpload: {
type: Boolean,
default () {
return true
}
}
},
data () {
const tmpArr = this.formData[this.item.model] || []
this.formData[this.item.model] = tmpArr
return {
project_id: '',
filesArr: tmpArr
}
},
methods: {
beforeUploadFile (file) {},
onSuccessFile (response, file, fileList) {
response.url = response.url || response.file || ''
response.sso_file_name = file.name
this.filesArr.push(response)
// this.$emit('onSubmit')
},
deleteFiles (index) {
this.filesArr.splice(index, 1)
// let temp = this.filesArr[index]
// deleteFile({
// action: this.item.deleteAction + '/' + temp.id + '?project_id=' + this.item.data.project_id,
// onError: () => {},
// onSuccess: (res) => {
// if (res.status === 200) {
// this.filesArr.splice(index, 1)
// }
// }
// })
}
},
watch: {
filesArr: {
immediate: true,
deep: true,
handler (value) {
if (this.formData[this.item.model].length !== value.length) {
this.formData[this.item.model] = value
}
}
}
}
}
</script>
<style lang="scss">
.u-babel {
display: inline-block;
margin-right: 10px;
}
.self-icon {
display: none !important;
vertical-align: middle;
margin-left: 10px;
font-size: 21px;
line-height: 22px;
}
.is-error .self-icon.el-icon-circle-close { display: inline-block !important; }
.is-success .self-icon.el-icon-circle-check { display: inline-block !important; }
.show-file {
position: relative;
float: left;
margin-right: 10px;
.close {
position: absolute;
z-index: 10;
right: -10px;
top: -10px;
width: 20px;
height: 20px;
color: #fff;
font-size: 12px;
line-height: 20px;
text-align: center;
background: #efefef;
border-radius: 50%;
cursor: pointer;
}
.el-avatar {
img {
width: 100%;
}
}
.title {
position: absolute;
left: 0;
bottom: 8px;
width: 100%;
padding-left: 5px;
font-size: 12px;
line-height: 20px;
background: #efefef;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
box-sizing: border-box;
}
.hover {
display: none;
position: absolute;
z-index: 9;
top: 0;
left: 0;
height: 100px;
width: 100%;
background: rgba(0, 0, 0, 0.2);
line-height: 100px;
text-align: center;
a {
color: #f1f1f1;
}
}
&:hover {
.hover {
display: block;
}
}
}
</style>
......@@ -5,6 +5,7 @@ import VueI18n from 'vue-i18n' // 使用 国际化
import createI18n from './assets/languages' // 国际化定义
import App from './app.vue' // 初始化 vue页面
import UploadForm from './components/upload-form'
import './style.scss' // 公共样式
import MetaInfo from 'vue-meta-info'
import Element from 'element-ui'
......@@ -19,6 +20,8 @@ require('promise.prototype.finally').shim()
/* 兼容处理 end */
Vue.use(VueRouter)
Vue.use(UploadForm)
Vue.component(UploadForm.name, UploadForm)
const router = createRouter()
Vue.use(VueI18n)
const i18n = createI18n()
......
<template>
<div class="play-paper">
<div class="play-paper">
<div class="play-paper-body">
<div class="play-paper-title"><div><h3>{{exam.title}}</h3></div></div>
<template v-if="status.examinationStatus === '00'">
<div class="no-exam">暂无考试</div>
</template>
<template v-else-if="status.examinationStatus === '90'">
<div class="play-paper-title">
<div>
<h3>{{exam.title}}</h3>
</div>
</div>
<template v-if="status.type === 2 && status.isPublished === 1">
<template v-if="exam.score.total !== undefined">
<div
style="font-size: 18px;"
>得分:单选:{{exam.score.radio}}分,多选:{{exam.score.checkbox}}分,简答:{{exam.score.shortAnswer}}分,总分:{{exam.score.total}}</div>
</template>
<template v-if="exam.shortAnswerList.length">
<template v-for="(item, index) in exam.shortAnswerList">
<template v-if="item.check_comment">
<div style="font-size: 18px; margin-top: 10px;" :key="index">
<div>简答题{{index+1}}</div>
<div>
老师评语:
<div style="display: inline-block" v-html="item.check_comment || ''"></div>
</div>
</div>
</template>
</template>
</template>
</template>
<template v-else-if="status.type === 0">
<div class="no-exam">考试结束 - 尚未进行考试</div>
</template>
<template v-else>
<div class="no-exam">考试结束 - 试卷批改中,请耐心等待</div>
</template>
</template>
<template v-else>
<div class="play-paper-title">
<div>
<h3>{{exam.title}}</h3>
</div>
</div>
<template v-if="status.isStart">
<div class="play-paper-content play-chapter-exam">
<template v-if="exam.id">
<div class='exam'>
<div style='text-align: center;'>
<div class='topic'>
<div class="exam">
<!-- <div style='text-align: center;'> -->
<!-- <div class='topic'> -->
<!-- <div class='tit'>{{exam.title}}</div> -->
<template v-if='exam.score.total !== undefined'><div class='cur'>单选:{{exam.score.radio}}分,多选:{{exam.score.checkbox}}分,简答:{{exam.score.shortAnswer}}分,总分:{{exam.score.total}}</div></template>
<template v-if="exam.type === 2 && exam.isPublished === 1">
<template v-if="exam.score.total !== undefined">
<div
style="font-size: 18px;"
>得分:单选:{{exam.score.radio}}分,多选:{{exam.score.checkbox}}分,简答:{{exam.score.shortAnswer}}分,总分:{{exam.score.total}}</div>
</template>
<template v-if="exam.shortAnswerList.length">
<template v-for="(item, index) in exam.shortAnswerList">
<template v-if="item.check_comment">
<div style="font-size: 18px; margin-top: 10px;" :key="index">
<div>简答题{{index+1}}</div>
<div>
老师评语:
<div style="display: inline-block" v-html="item.check_comment || ''"></div>
</div>
</div>
</template>
</template>
</template>
</template>
<template v-else-if="exam.type === 1 || exam.type === 2">
<div class="no-exam">试卷批改中,请耐心等待</div>
</template>
<!-- </div> -->
<!-- </div> -->
<template v-if="(exam.type !== 1 && exam.type !== 2)">
<div style="text-align: center;">考试截止时间为:{{status.terminateTime}}</div>
<!-- 单选题 -->
<template v-if='exam.radioList.length'>
<template v-for='(item, index) in exam.radioList'>
<div v-bind:key="item.id" class='q-group' :data-index='index'>
<div class='q-num'>{{index+1}}.</div><div class='q-title' v-html='item.content'></div><div class='q-type'>(单选题)</div>
<el-radio-group class='radio-group' v-model="item.user_answer">
<template v-for='(item1, index1) in item.options'>
<el-radio v-bind:key="item1.id" :label='item1.id' :disabled='!!item.right_answer && !!exam.type' :class='["radio", ((item.right_answer && !!exam.type) ? (item1.id === item.right_answer ? "success" : "error") : "")]'>{{ index1 | getLetter() }}. {{item1.option}}</el-radio>
<template v-if="exam.radioList.length">
<template v-for="(item, index) in exam.radioList">
<div v-bind:key="item.id" class="q-group" :data-index="index">
<div class="q-num">{{index+1}}.</div>
<div class="q-title" v-html="item.content"></div>
<div class="q-type">(单选题)</div>
<el-radio-group class="radio-group" v-model="item.user_answer">
<template v-for="(item1, index1) in item.options">
<el-radio
v-bind:key="item1.id"
:label="item1.id"
:disabled="!!item.right_answer && !!exam.type"
:class="['radio', ((item.right_answer && !!exam.type) ? (item1.id === item.right_answer ? 'success' : 'error') : '')]"
>{{ index1 | getLetter() }}. {{item1.option}}</el-radio>
</template>
</el-radio-group>
<template v-if='item.right_answer && !!exam.type'><div class='result'>学生答案:<div :class='["stu", (item.right_answer === item.user_answer ? "success" : "error")]'>{{ item.user_answer | getRadioAnswer(item.options) }}</div>&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getRadioAnswer(item.options) }}</div></template>
<template v-if="item.right_answer && !!exam.type">
<div class="result">
学生答案:
<div
:class="['stu', (item.right_answer === item.user_answer ? 'success' : 'error')]"
>{{ item.user_answer | getRadioAnswer(item.options) }}</div>
&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getRadioAnswer(item.options) }}
</div>
</template>
</div>
</template>
</template>
<!-- 多选题 -->
<template v-if='exam.checkboxList.length'>
<template v-for='(item, index) in exam.checkboxList'>
<div v-bind:key="item.id" class='q-group' :data-index='index'>
<div class='q-num'>{{exam.radioList.length+index+1}}.</div><div class='q-title' v-html='item.content'></div><div class='q-type'>(多选题)</div>
<el-checkbox-group class='checkbox-group' v-model="item.user_answer">
<template v-for='(item1, index1) in item.options'>
<el-checkbox v-bind:key="item1.id" :label='item1.id' :disabled='!!item.right_answer.length && !!exam.type' :class='["checkbox", ((item.right_answer.length && !!exam.type) ? (isCheckboxChecked(item1.id, item.right_answer) ? "success" : "error") : "")]'>{{ index1 | getLetter() }}. {{item1.option}}</el-checkbox>
<template v-if="exam.checkboxList.length">
<template v-for="(item, index) in exam.checkboxList">
<div v-bind:key="item.id" class="q-group" :data-index="index">
<div class="q-num">{{exam.radioList.length+index+1}}.</div>
<div class="q-title" v-html="item.content"></div>
<div class="q-type">(多选题)</div>
<el-checkbox-group class="checkbox-group" v-model="item.user_answer">
<template v-for="(item1, index1) in item.options">
<el-checkbox
v-bind:key="item1.id"
:label="item1.id"
:disabled="!!item.right_answer.length && !!exam.type"
:class="['checkbox', ((item.right_answer.length && !!exam.type) ? (isCheckboxChecked(item1.id, item.right_answer) ? 'success' : 'error') : '')]"
>{{ index1 | getLetter() }}. {{item1.option}}</el-checkbox>
</template>
</el-checkbox-group>
<template v-if='item.right_answer.length && !!exam.type'><div class='result'>学生答案:<div :class='["stu", ((item.right_answer.length && isCheckboxRight(item.user_answer, item.right_answer)) ? "success" : "error")]'>{{ item.user_answer | getCheckboxAnswer(item.options) }}</div>&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getCheckboxAnswer(item.options) }}</div></template>
<template v-if="item.right_answer.length && !!exam.type">
<div class="result">
学生答案:
<div
:class="['stu', ((item.right_answer.length && isCheckboxRight(item.user_answer, item.right_answer)) ? 'success' : 'error')]"
>{{ item.user_answer | getCheckboxAnswer(item.options) }}</div>
&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getCheckboxAnswer(item.options) }}
</div>
</template>
</div>
</template>
</template>
<!-- 简答题 -->
<template v-if='exam.shortAnswerList.length'>
<template v-for='(item, index) in exam.shortAnswerList'>
<div v-bind:key="index" class='q-group'>
<div class='q-sa-title'>{{exam.radioList.length+exam.checkboxList.length+index+1}}.&nbsp;&nbsp;简答题</div>
<template v-if="exam.shortAnswerList.length">
<template v-for="(item, index) in exam.shortAnswerList">
<div class="q-group" :key="index">
<div
class="q-sa-title"
>{{exam.radioList.length+exam.checkboxList.length+index+1}}.&nbsp;&nbsp;简答题</div>
<div class="edit_html" v-html="item.content || ''"></div>
<textarea :id="('editor-exam' + index)" v-model="item.user_answer"></textarea>
<div style="height: 10px;"></div>
<component :is="item.upload.type" v-bind:key="item.upload.model" :item="item.upload" :formData="item" :isUpload="!exam.type"></component>
<!-- 利用key值自动更新组件 -->
<component
:is="item.upload.type"
v-bind:key="item.upload.id + new Date().getTime()"
:item="item.upload"
:formData="item"
:isUpload="!exam.type"
></component>
</div>
</template>
</template>
<div :class='["btn", (exam.type && "on")]' @click='submitExam' :data-submit='!!exam.type' @mousedown='_SubmitMouseLeftDown()'>{{exam.type ? "已提交" : "提交"}}</div>
<div class='care'>(注意:测试只有一次提交机会)</div>
<div
:class="['btn', (exam.type && 'on')]"
@click="submitExam"
:data-submit="!!exam.type"
@mousedown="_SubmitMouseLeftDown()"
>{{exam.type ? "已提交" : "提交"}}</div>
<div class="care">(注意:考试只有一次提交机会)</div>
<!-- <div :class='["btn"]' @click='repeatExam($event, true)' v-if="exam.work_contents">重做</div> -->
</template>
</div>
</template>
</div>
</template>
<template v-else>
<div class="exam">
<p>考试须知:</p>
<div style="text-align: left;">考试开始时间为:{{status.startTime}}</div>
<div style="text-align: left;">考试截止时间为:{{status.terminateTime}}</div>
<template
v-if="new Date(status.startTime).getTime() - new Date(status.serverTime).getTime() <= 0"
>
<div
style="width: 25%;"
:class="['btn']"
@click="beginExam(true)"
@mousedown="beginExam(true)"
>开始考试</div>
</template>
</div>
</template>
</template>
</div>
</div>
</div>
</template>
<script>
......@@ -68,21 +199,34 @@ import Base64 from 'Base64'
import CKEDITOR from 'CKEDITOR'
var getLetter = (val) => {
var getLetter = val => {
switch (val) {
case 0: return 'A'
case 1: return 'B'
case 2: return 'C'
case 3: return 'D'
case 4: return 'E'
case 5: return 'F'
case 6: return 'G'
case 7: return 'H'
case 8: return 'I'
case 9: return 'J'
case 10: return 'K'
case 11: return 'L'
case 12: return 'M'
case 0:
return 'A'
case 1:
return 'B'
case 2:
return 'C'
case 3:
return 'D'
case 4:
return 'E'
case 5:
return 'F'
case 6:
return 'G'
case 7:
return 'H'
case 8:
return 'I'
case 9:
return 'J'
case 10:
return 'K'
case 11:
return 'L'
case 12:
return 'M'
}
}
......@@ -115,13 +259,13 @@ export default {
return str.substr(0, str.length - 1)
}
},
data () {
data() {
return {
_time: null, // 定时器,自动化提交
exam: {
// id: '1',
// title: '标题',
// type: 0, // 0: 暂存,可以继续答题;1: 提交,不能再继续答题
// type: 0, // 0: 暂存,可以继续答题;1: 提交,不能再继续答题(等待批改) 2: 已批改(这时候有分数)
// radioList: [
// {
// id: '6622309081933676544',
......@@ -242,31 +386,35 @@ export default {
// }
// }
// ]
}
}
},
mounted () {
this.loadAjax()
if (this._time) {
clearInterval(this._time)
this._time = null
status: {
isStart: false,
startTime: '',
terminateTime: '',
serverTime: '',
examinationStatus: '',
type: 0,
isPublished: 0
}
this._time = setInterval(() => {
if (!this.exam.type) {
this.submitExam({ submitType: true }) // 暂存, submitType: true 暂存;其他或不填为提交
} else {
clearInterval(this._time)
this._time = null
}
}, 30000)
},
destroyed () {
mounted() {
this.init()
this.$emit('changeSideBar', '')
setTimeout(() => {
if (window.document.getElementById('switch-btn')) {
window.document.getElementById('switch-btn').style.display = 'none'
}
}, 500)
},
destroyed() {
if (this._time) {
console.log(11)
clearInterval(this._time)
this._time = null
}
if (window.document.getElementById('switch-btn')) {
window.document.getElementById('switch-btn').style.display = 'block'
}
},
methods: {
isCheckboxRight: (val, arr) => {
......@@ -279,7 +427,10 @@ export default {
break
}
}
if (j === val.length) { flag = false; break }
if (j === val.length) {
flag = false
break
}
}
return flag
},
......@@ -292,11 +443,15 @@ export default {
}
return false
},
initckeditor () {
if (!this.exam.shortAnswerList) { return }
initckeditor() {
if (!this.exam.shortAnswerList) {
return
}
/* 删除所有 ckeditor 实例 */
const instances = CKEDITOR.instances
for (const name in instances) { instances[name].destroy() }
for (const name in instances) {
instances[name].destroy()
}
for (let i = 0; i < this.exam.shortAnswerList.length; i++) {
if (!instances['editor-exam' + i]) {
CKEDITOR.replace('editor-exam' + i, {
......@@ -305,16 +460,51 @@ export default {
filebrowserImageUploadUrl: '/api/ckeditor/img/upload',
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar: [
// { name: 'document', items: ['Source', '-', 'Save', 'NewPage', 'Preview'] },
{ name: 'styles', items: ['Styles', 'Format', 'Font', 'FontSize'] },
// { name: 'document', items: [ 'Source', '-', 'Save', 'NewPage', 'Preview' ] },
{
name: 'styles',
items: ['Styles', 'Format', 'Font', 'FontSize']
},
{ name: 'colors', items: ['TextColor', 'BGColor'] },
{ name: 'tools', items: ['Maximize', 'ShowBlocks'] },
// { name: 'clipboard', items: ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo'] },
// { name: 'clipboard', items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] },
{ name: 'editing', items: ['Find', 'Replace'] },
// { name: 'forms', items: ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'] },
// { name: 'forms', items: [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
'/',
{ name: 'basicstyles', items: ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat'] },
{ name: 'paragraph', items: ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl'] },
{
name: 'basicstyles',
items: [
'Bold',
'Italic',
'Underline',
'Strike',
'Subscript',
'Superscript',
'-',
'RemoveFormat'
]
},
{
name: 'paragraph',
items: [
'NumberedList',
'BulletedList',
'-',
'Outdent',
'Indent',
'-',
'Blockquote',
'CreateDiv',
'-',
'JustifyLeft',
'JustifyCenter',
'JustifyRight',
'JustifyBlock',
'-',
'BidiLtr',
'BidiRtl'
]
},
{ name: 'links', items: ['Link', 'Unlink', 'Anchor'] },
{ name: 'insert', items: ['Image', 'Table', 'HorizontalRule'] }
]
......@@ -323,49 +513,169 @@ export default {
this.exam.shortAnswerList[i].ckeditor = instances['editor-exam' + i]
}
},
/**
* 生命周期函数--监听页面加载
*/
loadAjax () {
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
if (this.id !== '0') {
cAction.examAction.getExamAnswer(this.cid, this.sid, this.id).then(_data => {
if (_data.code === 8001) {
cAction.examAction.getExamInfo(this.cid, this.sid).then(_data => {
this.exam = _data
this.exam.id = this.id
}).catch(e => { this.$message.error(e.message) }).finally(() => {
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
loading.close()
this.initckeditor()
})
return
init() {
this.loadExamStatus()
this.loadExamInfo()
if (this._time) {
clearInterval(this._time)
this._time = null
}
this._time = setInterval(() => {
this.loadExamStatus()
if (!this.exam.type && this.status.isStart) {
// console.log(11, '暂存')
this.submitExam({ submitType: true }) // 暂存, submitType: true 暂存;其他或不填为提交
}
/* 到时间 自动提交 */
if (
!this.exam.type &&
this.status.isStart &&
new Date(this.status.terminateTime).getTime() -
new Date(this.status.serverTime).getTime() <=
5000
) {
this.submitExam({ submitType: false, currentTarget: { dataset: {} } })
}
}, 3000)
},
/* 定时调用 - 考试状态 */
loadExamStatus() {
cAction.Player.getExamStatus(this.cid, this.sid, this.id)
.then(_data => {
if (_data.status && _data.status === 200) {
this.status.startTime = this.setTime(_data.start_time)
this.status.terminateTime = _data.terminate_time
this.status.serverTime = this.setTime(_data.server_time)
this.status.examinationStatus = _data.examination_status
} else {
this.$message.error('数据异常,请联系管理员')
}
})
.catch(e => {
this.$message.error(e.message)
})
.finally(() => {})
},
/* 时间格式化(解决safari时间兼容问题); */
setTime(time) {
return time.replace(/-/g, '/')
},
/* 开始考试 */
beginExam(flag) {
this.status.isStart = true
this.loadAjax(flag)
},
/* 加载题库基本数据 */
loadExamInfo() {
const loading = this.$loading({
lock: true,
text: '',
spinner: '',
background: 'rgba(255, 255, 255, 0.9)'
})
cAction.Player.getExamInfo(this.cid, this.sid)
.then(_data => {
this.exam = _data
this.exam.id = this.id
}).catch(e => { this.$message.error(e.message) }).finally(() => {
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
})
.catch(e => {
this.$message.error(e.message)
})
.finally(() => {
loading.close()
this.initckeditor()
/* 正在考试,考试结束 */
if (
this.status.examinationStatus === '20' ||
this.status.examinationStatus === '90'
) {
this.beginExam()
}
})
},
/**
* 生命周期函数--监听页面加载
* @param flag 通过该字段 判别是点击进入考试 还是 自动调用
*/
loadAjax(flag) {
const loading = this.$loading({
lock: true,
text: '',
spinner: '',
background: 'rgba(255, 255, 255, 0.9)'
})
cAction.Player.getExamAnswer(this.cid, this.sid, this.id)
.then(_data => {
if (_data.code === 8001) {
console.log(
'没有考试内容,认为是第一次答题,并且在答题期间,所以显示考试开始页面'
)
if (!flag) {
this.status.isStart = false
}
} else {
cAction.examAction.getExamInfo(this.cid, this.sid).then(_data => {
this.exam = _data
this.exam.id = this.id
}).catch(e => { this.$message.error(e.message) }).finally(() => {
// this.exam.id = _data.id
this.exam.title = _data.title
this.exam.type = _data.type
this.exam.score = _data.score
this.exam.submitted_time = _data.submitted_time
this.exam.isPublished = _data.isPublished
this.status.type = _data.type
this.status.isPublished = _data.isPublished
for (let i = 0; i < this.exam.radioList.length; i++) {
for (let j = 0; j < _data.radioList.length; j++) {
if (_data.radioList[j].id === this.exam.radioList[i].id) {
for (const k in _data.radioList[j]) {
this.exam.radioList[i][k] = _data.radioList[j][k]
}
}
}
}
for (let i = 0; i < this.exam.checkboxList.length; i++) {
for (let j = 0; j < _data.checkboxList.length; j++) {
if (_data.checkboxList[j].id === this.exam.checkboxList[i].id) {
for (const k in _data.checkboxList[j]) {
this.exam.checkboxList[i][k] = _data.checkboxList[j][k]
}
}
}
}
for (let i = 0; i < this.exam.shortAnswerList.length; i++) {
for (let j = 0; j < _data.shortAnswerList.length; j++) {
if (
_data.shortAnswerList[j].id ===
this.exam.shortAnswerList[i].id
) {
for (const k in _data.shortAnswerList[j]) {
this.exam.shortAnswerList[i][k] =
_data.shortAnswerList[j][k]
}
}
}
}
}
})
.catch(e => {
this.$message.error(e.message)
})
.finally(() => {
// console.log(this.exam.type, this.exam.isPublished)
loading.close()
if (
this.status.isStart &&
this.exam.type !== 1 &&
this.exam.type !== 2
) {
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
loading.close()
this.initckeditor()
})
}
})
},
/**
* 提交试题
*/
submitExam (e) {
submitExam(e) {
if (!e.submitType && e.currentTarget.dataset.submit) {
this.$message.error('已做过,不能再提交')
return
......@@ -405,28 +715,49 @@ export default {
}
body.answers.shortAnswerList.push({
id: tmp.id,
user_answer: Base64.encode(tmp.user_answer, 'utf-8'),
user_answer: Base64.encode(tmp.user_answer),
attachments: tmp.attachments
})
}
body.answers = JSON.stringify(body.answers)
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.examAction.submitExam(this.cid, this.sid, this.exam.id, body).then(_res => {
let loading = null
if (!e.submitType) {
loading = this.$loading({
lock: true,
text: '',
spinner: '',
background: 'rgba(255, 255, 255, 0.9)'
})
}
cAction.Player.submitExam(this.cid, this.sid, this.exam.id, body)
.then(_res => {
if (e.submitType) {
this.$message.success('暂存成功')
// this.$message.success('暂存成功')
console.log('暂存成功')
return
}
if (_res.code === 200) {
this.loadAjax()
this.$message.success('考试答卷提交成功')
// this.exam.type = 1
this.init()
console.log(111, 'this.loadAjax()')
} else {
this.$message.error(_res.data.error)
}
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
})
.catch(e => {
this.$message.error(e.message)
})
.finally(() => {
if (!e.submitType) {
loading.close()
}
})
},
_SubmitMouseLeftDown () {
_SubmitMouseLeftDown() {
const _fn1 = this.repeatExam.bind(this, false)
document.addEventListener('keydown', _fn1, false)
const _fn3 = function () {
const _fn3 = function() {
document.removeEventListener('keydown', _fn1)
document.removeEventListener('mouseup', _fn3)
}
......@@ -435,25 +766,38 @@ export default {
/**
* 重做
*/
repeatExam (e, flag) {
repeatExam(e, flag) {
let _flag = flag
/* 字母 f */
if (e.keyCode === 70) {
_flag = true
}
if (!_flag) { return }
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.Player.getExamDetail(this.sid, this.cid, this.id).then(_data => {
if (!_flag) {
return
}
const loading = this.$loading({
lock: true,
text: '',
spinner: '',
background: 'rgba(255, 255, 255, 0.9)'
})
cAction.chapterAction
.getExamDetail(this.sid, this.cid, this.id)
.then(_data => {
this.exam = {}
}).catch(e => { this.$message.error(e.message) }).finally(() => {
})
.catch(e => {
this.$message.error(e.message)
})
.finally(() => {
loading.close()
})
}
},
watch: {
id: {
handler () {
this.loadAjax()
handler() {
this.init()
}
}
}
......@@ -462,29 +806,124 @@ export default {
<style lang="scss" scoped>
.play {
.exam { padding: 0; }
.exam .topic { display: inline-block; margin-bottom: 0.1rem; }
.exam .topic .tit { margin: 0 auto; padding: 0 0.2rem; text-align: center; font-size: 0.24rem; color: #313131; background: #fff; box-sizing: border-box; -webkit-box-sizing: border-box; }
.exam .topic .cur { text-align: center; font-size: 0.18rem; color: #313131; line-height: 0.4rem; }
.exam {
padding: 0;
}
.exam .topic {
display: inline-block;
margin-bottom: 0.1rem;
}
.exam .topic .tit {
margin: 0 auto;
padding: 0 0.2rem;
text-align: center;
font-size: 0.24rem;
color: #313131;
background: #fff;
box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.exam .topic .cur {
text-align: center;
font-size: 0.18rem;
color: #313131;
line-height: 0.4rem;
}
/* 循环 所有选择题 */
.exam .q-group { padding: 0.1rem 0.1rem; border-bottom: 1px solid #c9c9c97a; overflow: hidden; }
.exam .q-group .q-num { float: left; margin-right: 0.1rem; font-size: 0.16rem; color: #676a6c; }
.exam .q-group .q-title { float: left; width: 90%; font-size: 0.16rem; color: #676a6c; text-align: justify; }
.exam .q-group .q-type { float: right; font-size: 0.16rem; color: #676a6c; }
.exam .q-group .radio-group { float: left; margin-top: 0.1rem; width: 100%; }
.exam .q-group .radio-group .radio { display: block; font-size: 0.18rem; color: #3f3b3a; line-height: 0.3rem; margin-bottom: 0.1rem; }
.exam .q-group .checkbox-group { float: left; margin-top: 0.1rem; width: 100%; }
.exam .q-group .checkbox-group .checkbox { display: block; font-size: 0.18rem; color: #3f3b3a; line-height: 0.3rem; margin-bottom: 0.1rem; }
.exam .q-group .radio-group .radio.error, .exam .q-group .checkbox-group .checkbox.error { color: #d80000; }
.exam .q-group .radio-group .radio.success, .exam .q-group .checkbox-group .checkbox.success { color: #090; }
.exam .q-group .result { float: right; font-size: 0.18rem; color: #3f3b3a; margin-right: 0; }
.exam .q-group .result .stu { display: inline-block; }
.exam .q-group .result .stu.error { color: #d80000; }
.exam .q-group .result .stu.success { color: #090; }
.exam .q-group:last-child { border-bottom: none; }
.exam .btn { margin: 0.2rem auto; width: 60%; height: 0.5rem; line-height: 0.5rem; font-size: 0.16rem; text-align: center; font-weight: 300; color: #fff; border-radius: 0.1rem; background: #b49441; cursor: pointer; }
.exam .btn.on { opacity: 0.5; }
.exam .care { font-size: 0.16rem; color: #d80000; text-align: center; }
.exam .q-group {
padding: 0.1rem 0.1rem;
border-bottom: 1px solid #c9c9c97a;
overflow: hidden;
}
.exam .q-group .q-num {
float: left;
margin-right: 0.1rem;
font-size: 0.16rem;
color: #676a6c;
}
.exam .q-group .q-title {
float: left;
width: 90%;
font-size: 0.16rem;
color: #676a6c;
text-align: justify;
}
.exam .q-group .q-type {
float: right;
font-size: 0.16rem;
color: #676a6c;
}
.exam .q-group .radio-group {
float: left;
margin-top: 0.1rem;
width: 100%;
}
.exam .q-group .radio-group .radio {
display: block;
font-size: 0.18rem;
color: #3f3b3a;
line-height: 0.3rem;
margin-bottom: 0.1rem;
}
.exam .q-group .checkbox-group {
float: left;
margin-top: 0.1rem;
width: 100%;
}
.exam .q-group .checkbox-group .checkbox {
display: block;
font-size: 0.18rem;
color: #3f3b3a;
line-height: 0.3rem;
margin-bottom: 0.1rem;
}
.exam .q-group .radio-group .radio.error,
.exam .q-group .checkbox-group .checkbox.error {
color: #d80000;
}
.exam .q-group .radio-group .radio.success,
.exam .q-group .checkbox-group .checkbox.success {
color: #090;
}
.exam .q-group .result {
float: right;
font-size: 0.18rem;
color: #3f3b3a;
margin-right: 0;
}
.exam .q-group .result .stu {
display: inline-block;
}
.exam .q-group .result .stu.error {
color: #d80000;
}
.exam .q-group .result .stu.success {
color: #090;
}
.exam .q-group:last-child {
border-bottom: none;
}
.exam .btn {
margin: 0.2rem auto;
width: 60%;
height: 0.5rem;
line-height: 0.5rem;
font-size: 0.16rem;
text-align: center;
font-weight: 300;
color: #fff;
border-radius: 0.1rem;
background: #b49441;
cursor: pointer;
}
.exam .btn.on {
opacity: 0.5;
}
.exam .care {
font-size: 0.16rem;
color: #d80000;
text-align: center;
}
.exam .q-sa-title {
float: left;
......@@ -494,4 +933,9 @@ export default {
text-align: justify;
}
}
.no-exam {
font-size: 0.24rem;
line-height: 19;
text-align: center;
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论