提交 ede512a4 authored 作者: GOD_ZYX's avatar GOD_ZYX

开发 考试exam模块,前端开发完成,后端尚未完成

上级 8a3a33b7
<template>
<div class="play-paper">
<div class="play-paper-body">
<div class="play-paper-title"><div><h3>{{chapterName}}</h3></div></div>
<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='tit'>{{exam.title}}</div> -->
<template v-if='exam.score'><div class='cur'>正确率:{{exam.score}}%</div></template>
</div>
</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' :class='["radio", (item.right_answer ? (item1.id === item.user_answer ? "success" : "error") : "")]'>{{ index1 | getLetter() }}. {{item1.option}}</el-radio>
</template>
</el-radio-group>
<template v-if='item.right_answer'><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' :class='["checkbox", (item.right_answer.length ? (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'><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 class='q-group'>
<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"></component>
</div>
</template>
</template>
<div :class='["btn", (exam.submitted_time && "on")]' @click='submitExam' :data-submit='!!exam.submitted_time' @mousedown='_SubmitMouseLeftDown()'>{{exam.submitted_time ? "已提交" : "提交"}}</div>
<div class='care'>(注意:测试只有一次提交机会)</div>
<!-- <div :class='["btn"]' @click='repeatExam($event, true)' v-if="exam.work_contents">重做</div> -->
</div>
</template>
</div>
</div>
</div>
</template>
<script>
import cAction from '@actions'
import Base64 from 'Base64'
import CKEDITOR from 'CKEDITOR'
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'
}
}
export default {
props: {
chapterId: { type: String, require: false },
chapterName: { type: String, require: false },
chapterExam: { type: Object, require: false },
sid: { type: String, require: false },
cid: { type: String, require: false },
id: { type: String, require: false }
},
filters: {
getLetter: getLetter,
getRadioAnswer: (val, arr) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i].id === val) {
return getLetter(i)
}
}
},
getCheckboxAnswer: (val, arr) => {
let str = ''
for (let i = 0; i < val.length; i++) {
let tmpId = val[i]
for (let j = 0; j < arr.length; j++) {
if (arr[j].id === tmpId) {
str += getLetter(j) + ','
break
}
}
}
return str.substr(0, str.length - 1)
}
},
data () {
return {
exam: {
id: '1',
title: '标题',
radioList: [
{
id: '6622309081933676544',
content: '\u5047\u8bbe\u8d27\u5e01\u9700\u6c42\u4e3aL=ky-hr\uff0c\u8d27\u5e01\u4f9b\u7ed9\u589e\u52a010\u4ebf\u7f8e\u5143\u800c\u5176\u5b83\u6761\u4ef6\u4e0d\u53d8\uff0c\u5219\u4f1a\u4f7fLM \u66f2\u7ebf( )',
options: [
{
id: '6622310260604403712',
option: '\u53f3\u79fb10\u4ebf\u7f8e\u5143'
},
{
id: '6622310260604403713',
option: '\u53f3\u79fb k\u4e58\u4ee510\u4ebf\u7f8e\u5143'
},
{
id: '6622310260604403714',
option: '\u53f3\u79fb10\u4ebf\u7f8e\u5143\u9664\u4ee5k'
},
{
id: '6622310260604403715',
option: '\u53f3\u79fb k\u9664\u4ee510\u4ebf\u7f8e\u5143'
}
],
user_answer: '6622310260604403714',
right_answer: '6622310260604403714',
get_score: 30,
score: 30
}
],
checkboxList: [
{
id: '6622310510798831616',
content: '\u51ef\u6069\u65af\u5b8f\u89c2\u7ecf\u6d4e\u7406\u8bba\u7684\u4e3b\u8981\u524d\u63d0\u5305\u62ec\uff08\uff09',
options: [
{
id: '6622310872641437696',
option: '\u8fb9\u9645\u6d88\u8d39\u503e\u5411\u9012\u51cf'
},
{
id: '6622310872641437697',
option: '\u8d44\u672c\u8fb9\u9645\u6548\u7387\u9012\u51cf'
},
{
id: '6622310872641437698',
option: '\u4e0d\u786e\u5b9a\u6027\u4e0e\u6d41\u52a8\u6027\u504f\u597d'
},
{
id: '6622310872641437699',
option: '\u540d\u4e49\u5de5\u8d44\u521a\u6027'
}
],
user_answer: [
'6622310872641437697',
'6622310872641437698',
'6622310872641437699'
],
right_answer: [
'6622310872641437697',
'6622310872641437698'
],
get_score: 30,
score: 30
}
],
shortAnswerList: [
{
id: '6622311487476072448',
content: '\u8bba\u8ff01929\u5e74\u7f8e\u56fd\u4e3a\u4ec0\u4e48\u4f1a\u51fa\u73b0\u5927\u8427\u6761\u3002',
user_answer: '2018\u5e74\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u5bf9\u4f9b\u7ed9\u6027\u7ed3\u6784\u6027\u6539\u9769\u6709\u4e00\u5b9a\u7684\u63a8\u52a8\u4f5c\u7528\u3002\u623f\u5730\u4ea7\u5e02\u573a\u5f97\u5230\u4e00\u5b9a\u7a0b\u5ea6\u6291\u5236\u3002\u7ecf\u6d4e\u53d1\u5c55\u5e73\u7a33\uff0c\u7ecf\u6d4e\u8fd0\u884c\u5728\u5408\u7406\u533a\u95f4\u3002\u964d\u7a0e\u4e3e\u52a8\u53d6\u5f97\u6210\u6548\uff0c\u5168\u5e74\u56fd\u5185\u589e\u503c\u7a0e\u3001\u4f01\u4e1a\u6240\u5f97\u7a0e\u3001\u4e2a\u4eba\u6240\u5f97\u7a0e\u540c\u6bd4\u5206\u522b\u589e\u957f9.1%\u300110%\u300115.9%\uff0c\u5206\u522b\u62c9\u9ad8\u5168\u56fd\u8d22\u653f\u6536\u5165\u589e\u5e453\u4e2a\u30011.9\u4e2a\u30011.1\u4e2a\u767e\u5206\u70b9\uff0c\u6709\u53d1\u6325\u51fa\u5b8f\u89c2\u8c03\u63a7\u7684\u4f5c\u7528\u3002\u4ece2018\u5e74\u8d27\u5e01\u653f\u7b56\u6574\u4f53\u6765\u770b\uff0c\u8f83\u597d\u5730\u628a\u63e1\u4e86\u652f\u6301\u5b9e\u4f53\u7ecf\u6d4e\u548c\u517c\u987e\u5185\u5916\u90e8\u5747\u8861\u4e4b\u95f4\u7684\u5e73\u8861\uff0c\u53d6\u5f97\u4e86\u79ef\u6781\u6210\u6548\uff0c\u4e5f\u5f88\u597d\u5730\u9632\u8303\u4e86\u91d1\u878d\u98ce\u9669\u30022018\u5e74\u603b\u4f53\u4fdd\u6301\u4e86\u9002\u5b9c\u7684\u8d27\u5e01\u91d1\u878d\u73af\u5883\uff0c\u65e0\u8bba\u662f\u5404\u9879\u8d37\u6b3e\u8fd8\u662f\u666e\u60e0\u53e3\u5f84\u5c0f\u5fae\u8d37\u6b3e\u90fd\u540c\u6bd4\u5927\u5e45\u591a\u589e\uff0cM2\u548c\u793e\u4f1a\u878d\u8d44\u89c4\u6a21\u5b58\u91cf\u540c\u6bd4\u589e\u901f\u4e0e\u540d\u4e49GDP\u589e\u901f\u57fa\u672c\u5339\u914d\uff0c\u6709\u529b\u4fc3\u8fdb\u4e86\u6211\u56fd\u7ecf\u6d4e\u6301\u7eed\u5065\u5eb7\u53d1\u5c55\u3002\u6211\u56fd\u5b9e\u65bd\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u548c\u7a33\u5065\u7684\u8d27\u5e01\u653f\u7b56\uff0c\u4e00\u65b9\u9762\u80fd\u4ee5\u653f\u7b56\u7684\u7a33\u5b9a\u6765\u5e94\u5bf9\u5916\u90e8\u73af\u5883\u7684\u4e0d\u7a33\u5b9a\uff0c\u5728\u9762\u5bf9\u4e16\u754c\u73af\u5883\u53d8\u5316\u65f6\u80fd\u968f\u65f6\u628a\u63e1\u4f4f\u6218\u7565\u4e3b\u52a8\u6027\uff1b\u540c\u65f6\u80fd\u591f\u79ef\u6781\u4fc3\u8fdb\u6211\u56fd\u4f9b\u7ed9\u4fa7\u7ed3\u6784\u6027\u6539\u9769\u8fdb\u7a0b\uff0c\u652f\u6301\u89e3\u51b3\u7ecf\u6d4e\u53d1\u5c55\u4e2d\u7684\u6df1\u5c42\u6b21\u7ed3\u6784\u6027\u95ee\u9898\uff0c\u63d0\u5347\u6211\u56fd\u7ecf\u6d4e\u53d1\u5c55\u8d28\u91cf\u548c\u6548\u7387\u3002\u5b8f\u89c2\u653f\u7b56\u7684\u5b9e\u65bd\u4e5f\u5de9\u56fa\u4e86\u6211\u56fd\u5e73\u7a33\u53d1\u5c55\u7684\u7ecf\u6d4e\u57fa\u7840\uff0c\u4e3a\u5168\u9762\u5efa\u6210\u5c0f\u5eb7\u793e\u4f1a\u63d0\u4f9b\u4e86\u575a\u5f3a\u6709\u529b\u7684\u7ecf\u6d4e\u63aa\u65bd\u4fdd\u969c\u3002',
check_comment: '\u5361\u5c3c\u66fc\u8ba4\u4e3a\u4eba\u7684\u5927\u8111\u5b58\u5728\u4e24\u4e2a\u7cfb\u7edf\uff0c\u4e24\u4e2a\u7cfb\u7edf\u5206\u522b\u6709\u5feb\u4e0e\u6162\u4e24\u79cd\u4f5c\u51b3\u5b9a\u7684\u65b9\u5f0f\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u662f\u540c\u4e00\u8ba4\u8bc6\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u3002\u4e8c\u8005\u65e2\u76f8\u4e92\u5bf9\u7acb\u53c8\u76f8\u4e92\u7edf\u4e00\u3002\u6211\u4eec\u79f0\u4f5c\u7cfb\u7edf1\u548c\u7cfb\u7edf2\uff0c\u4ed6\u7684\u7406\u8bba\u4e0e\u6211\u4eec\u719f\u77e5\u7684\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u7684\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u6709\u5f88\u76f8\u4f3c\u7684\u7406\u8bba\u6216\u8005\u8bf4\u662f\u4e0d\u662f\u5c5e\u4e8e\u540c\u4e00\u4e2a\u7406\u8bba\u57fa\u7840\u3002',
get_score: 30,
score: 40,
attachments: [],
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>
`
}
},
{
id: '6622311487476072448',
content: '\u8bba\u8ff01929\u5e74\u7f8e\u56fd\u4e3a\u4ec0\u4e48\u4f1a\u51fa\u73b0\u5927\u8427\u6761\u3002',
user_answer: '2018\u5e74\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u5bf9\u4f9b\u7ed9\u6027\u7ed3\u6784\u6027\u6539\u9769\u6709\u4e00\u5b9a\u7684\u63a8\u52a8\u4f5c\u7528\u3002\u623f\u5730\u4ea7\u5e02\u573a\u5f97\u5230\u4e00\u5b9a\u7a0b\u5ea6\u6291\u5236\u3002\u7ecf\u6d4e\u53d1\u5c55\u5e73\u7a33\uff0c\u7ecf\u6d4e\u8fd0\u884c\u5728\u5408\u7406\u533a\u95f4\u3002\u964d\u7a0e\u4e3e\u52a8\u53d6\u5f97\u6210\u6548\uff0c\u5168\u5e74\u56fd\u5185\u589e\u503c\u7a0e\u3001\u4f01\u4e1a\u6240\u5f97\u7a0e\u3001\u4e2a\u4eba\u6240\u5f97\u7a0e\u540c\u6bd4\u5206\u522b\u589e\u957f9.1%\u300110%\u300115.9%\uff0c\u5206\u522b\u62c9\u9ad8\u5168\u56fd\u8d22\u653f\u6536\u5165\u589e\u5e453\u4e2a\u30011.9\u4e2a\u30011.1\u4e2a\u767e\u5206\u70b9\uff0c\u6709\u53d1\u6325\u51fa\u5b8f\u89c2\u8c03\u63a7\u7684\u4f5c\u7528\u3002\u4ece2018\u5e74\u8d27\u5e01\u653f\u7b56\u6574\u4f53\u6765\u770b\uff0c\u8f83\u597d\u5730\u628a\u63e1\u4e86\u652f\u6301\u5b9e\u4f53\u7ecf\u6d4e\u548c\u517c\u987e\u5185\u5916\u90e8\u5747\u8861\u4e4b\u95f4\u7684\u5e73\u8861\uff0c\u53d6\u5f97\u4e86\u79ef\u6781\u6210\u6548\uff0c\u4e5f\u5f88\u597d\u5730\u9632\u8303\u4e86\u91d1\u878d\u98ce\u9669\u30022018\u5e74\u603b\u4f53\u4fdd\u6301\u4e86\u9002\u5b9c\u7684\u8d27\u5e01\u91d1\u878d\u73af\u5883\uff0c\u65e0\u8bba\u662f\u5404\u9879\u8d37\u6b3e\u8fd8\u662f\u666e\u60e0\u53e3\u5f84\u5c0f\u5fae\u8d37\u6b3e\u90fd\u540c\u6bd4\u5927\u5e45\u591a\u589e\uff0cM2\u548c\u793e\u4f1a\u878d\u8d44\u89c4\u6a21\u5b58\u91cf\u540c\u6bd4\u589e\u901f\u4e0e\u540d\u4e49GDP\u589e\u901f\u57fa\u672c\u5339\u914d\uff0c\u6709\u529b\u4fc3\u8fdb\u4e86\u6211\u56fd\u7ecf\u6d4e\u6301\u7eed\u5065\u5eb7\u53d1\u5c55\u3002\u6211\u56fd\u5b9e\u65bd\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u548c\u7a33\u5065\u7684\u8d27\u5e01\u653f\u7b56\uff0c\u4e00\u65b9\u9762\u80fd\u4ee5\u653f\u7b56\u7684\u7a33\u5b9a\u6765\u5e94\u5bf9\u5916\u90e8\u73af\u5883\u7684\u4e0d\u7a33\u5b9a\uff0c\u5728\u9762\u5bf9\u4e16\u754c\u73af\u5883\u53d8\u5316\u65f6\u80fd\u968f\u65f6\u628a\u63e1\u4f4f\u6218\u7565\u4e3b\u52a8\u6027\uff1b\u540c\u65f6\u80fd\u591f\u79ef\u6781\u4fc3\u8fdb\u6211\u56fd\u4f9b\u7ed9\u4fa7\u7ed3\u6784\u6027\u6539\u9769\u8fdb\u7a0b\uff0c\u652f\u6301\u89e3\u51b3\u7ecf\u6d4e\u53d1\u5c55\u4e2d\u7684\u6df1\u5c42\u6b21\u7ed3\u6784\u6027\u95ee\u9898\uff0c\u63d0\u5347\u6211\u56fd\u7ecf\u6d4e\u53d1\u5c55\u8d28\u91cf\u548c\u6548\u7387\u3002\u5b8f\u89c2\u653f\u7b56\u7684\u5b9e\u65bd\u4e5f\u5de9\u56fa\u4e86\u6211\u56fd\u5e73\u7a33\u53d1\u5c55\u7684\u7ecf\u6d4e\u57fa\u7840\uff0c\u4e3a\u5168\u9762\u5efa\u6210\u5c0f\u5eb7\u793e\u4f1a\u63d0\u4f9b\u4e86\u575a\u5f3a\u6709\u529b\u7684\u7ecf\u6d4e\u63aa\u65bd\u4fdd\u969c\u3002',
check_comment: '\u5361\u5c3c\u66fc\u8ba4\u4e3a\u4eba\u7684\u5927\u8111\u5b58\u5728\u4e24\u4e2a\u7cfb\u7edf\uff0c\u4e24\u4e2a\u7cfb\u7edf\u5206\u522b\u6709\u5feb\u4e0e\u6162\u4e24\u79cd\u4f5c\u51b3\u5b9a\u7684\u65b9\u5f0f\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u662f\u540c\u4e00\u8ba4\u8bc6\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u3002\u4e8c\u8005\u65e2\u76f8\u4e92\u5bf9\u7acb\u53c8\u76f8\u4e92\u7edf\u4e00\u3002\u6211\u4eec\u79f0\u4f5c\u7cfb\u7edf1\u548c\u7cfb\u7edf2\uff0c\u4ed6\u7684\u7406\u8bba\u4e0e\u6211\u4eec\u719f\u77e5\u7684\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u7684\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u6709\u5f88\u76f8\u4f3c\u7684\u7406\u8bba\u6216\u8005\u8bf4\u662f\u4e0d\u662f\u5c5e\u4e8e\u540c\u4e00\u4e2a\u7406\u8bba\u57fa\u7840\u3002',
get_score: 30,
score: 40,
attachments: [],
upload: {
type: 'upload-form',
label: '附件上传:',
model: 'attachments',
action: webConf.apiBaseURL + '/util/upload-file',
data: {
special: 'exam'
},
attrs: {
multiple: true
},
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>
`
}
}
]
}
}
},
mounted () {
this.loadAjax()
},
methods: {
isCheckboxRight: (val, arr) => {
let flag = true
for (let i = 0; i < val.length; i++) {
let tmpId = val[i]
let j = 0
for (; j < arr.length; j++) {
if (arr[j] === tmpId) {
break
}
}
if (j === arr.length) { flag = false; break }
}
return flag
},
isCheckboxChecked: (val, arr) => {
let i = 0
for (; i < arr.length; i++) {
if (arr[i].id === val || arr[i] === val) {
return true
}
}
if (i === arr.length) {
return false
}
},
initckeditor () {
for (let i = 0; i < this.exam.shortAnswerList.length; i++) {
let tmpEditor = CKEDITOR.replace('editor-exam' + i, {
height: 300,
uiColor: '#eeeeee',
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: 'colors', items: [ 'TextColor', 'BGColor' ] },
{ name: 'tools', items: [ 'Maximize', 'ShowBlocks' ] },
// { 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: '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' ] }
]
})
this.exam.shortAnswerList[i].ckeditor = tmpEditor
}
},
/**
* 生命周期函数--监听页面加载
*/
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 => {
this.exam = _data
}).catch(e => { this.$message.error(e.message) }).finally(() => {
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
loading.close()
this.initckeditor()
})
} else {
cAction.examAction.getExamInfo(this.cid, this.sid).then(_data => {
this.exam = _data
}).catch(e => { this.$message.error(e.message) }).finally(() => {
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
loading.close()
this.initckeditor()
})
}
},
/**
* 提交试题
*/
submitExam (e) {
if (e.currentTarget.dataset.submit) {
this.$message.error('已做过,不能再提交')
return
}
let body = { answers: {}, type: 0 } // type: 0 缓存;type: 1 提交
body.answers.radioList = []
for (let i = 0; i < this.exam.radioList.length; i++) {
let tmp = this.exam.radioList[i]
if (!tmp.user_answer) {
this.$message.error('还有单选题未做,不能提交')
return
}
body.answers.radioList.push({
id: tmp.id,
user_answer: tmp.user_answer
})
}
body.answers.checkboxList = []
for (let i = 0; i < this.exam.checkboxList.length; i++) {
let tmp = this.exam.checkboxList[i]
if (!tmp.user_answer.length) {
this.$message.error('还有多选题未做,不能提交')
return
}
body.answers.checkboxList.push({
id: tmp.id,
user_answer: tmp.user_answer
})
}
body.answers.shortAnswerList = []
for (let i = 0; i < this.exam.shortAnswerList.length; i++) {
let tmp = this.exam.shortAnswerList[i]
tmp.user_answer = tmp.ckeditor.getData()
if (!tmp.user_answer) {
this.$message.error('还有简答题未做,不能提交')
return
}
body.answers.shortAnswerList.push({
id: tmp.id,
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 => {
debugger
if (_res.success) {
} else {
this.$message.error(_res.data.error)
}
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
},
_SubmitMouseLeftDown () {
let _fn1 = this.repeatExam.bind(this, false)
document.addEventListener('keydown', _fn1, false)
let _fn3 = function () {
document.removeEventListener('keydown', _fn1)
document.removeEventListener('mouseup', _fn3)
}
document.addEventListener('mouseup', _fn3, false)
},
/**
* 重做
*/
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.chapterAction.getExamDetail(this.sid, this.cid, this.id).then(_data => {
this.exam = {}
}).catch(e => { this.$message.error(e.message) }).finally(() => {
this.setTime = setInterval(() => {
// console.log(this.chapterExam.work_id, this.id)
if (this.chapterExam.work_id && this.chapterExam.work_id === this.id) {
if (!this.exam.id) {
this.exam = this.updateData(this.chapterExam)
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
}
clearInterval(this.setTime)
}
}, 50)
loading.close()
})
}
},
watch: {
id: {
handler () {
this.loadAjax()
}
}
}
}
</script>
<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 .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;
width: 100%;
font-size: 0.16rem;
color: #676a6c;
text-align: justify;
}
}
</style>
import BaseAPI from '../base_api'
export default class ExamAPI extends BaseAPI {
/**
* 获取考卷信息
* @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
* @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' } })
}
import LoginAPI from './api/login_api'
import ChapterAPI from './api/chapter_api'
import ExamAPI from './api/exam_api'
import CourseAPI from './api/course_api'
import DiscussAPI from './api/discuss_api'
import MsgAPI from './api/msg_api'
......@@ -8,6 +9,7 @@ import ReportAPI from './api/report_api'
let loginApi = new LoginAPI(webConf)
let chapterApi = new ChapterAPI(webConf)
let examApi = new ExamAPI(webConf)
let courseApi = new CourseAPI(webConf)
let discussApi = new DiscussAPI(webConf)
let msgApi = new MsgAPI(webConf)
......@@ -17,6 +19,7 @@ let reportApi = new ReportAPI(webConf)
export {
loginApi,
chapterApi,
examApi,
courseApi,
discussApi,
msgApi,
......
## 组件简介
| 字段值 | 说明 | 字段属性 | 默认值 |
| ------- | ------------------------- | ------- | ----- |
| `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"
v-bind="item.attrs || {}">
<el-button type="primary" size="small">点击上传</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)">
<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)">
<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 {}
}
}
},
data () {
let 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)
// }
// }
// })
}
}
}
</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>
import { examApi } from '@services'
export default class ExamAction {
/* 获取考卷信息 */
getExamInfo (cid, sid) {
return examApi.getExamInfo(cid, sid).then(_res => {
let exam = {}
exam.id = _res.id
exam.title = _res.title
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 examApi.getExamAnswer(cid, sid, eid).then(_res => {
let exam = {}
exam.id = _res.id
exam.title = _res.title
exam.radioList = _res.sheet.radioList
for (let i = 0; i < exam.radioList.length; i++) {
let 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++) {
let 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++) {
let tmp = exam.shortAnswerList[i]
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',
headers: {
'tenant': 'sofia'
}
},
attrs: {
multiple: true
},
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
})
}
/* 提交考卷 */
submitExam (cid, sid, eid, obj) {
return examApi.submitExam(cid, sid, eid, obj).then(_res => {
return _res
})
}
}
import LoginAction from './LoginAction'
import ChapterAction from './ChapterAction'
import ExamAction from './ExamAction'
import CourseAction from './CourseAction'
import DiscussAction from './DiscussAction'
import MsgAction from './MsgAction'
......@@ -8,6 +9,7 @@ import ReportAction from './ReportAction'
let loginAction = new LoginAction()
let chapterAction = new ChapterAction()
let examAction = new ExamAction()
let courseAction = new CourseAction()
let discussAction = new DiscussAction()
let msgAction = new MsgAction()
......@@ -17,6 +19,7 @@ let reportAction = new ReportAction()
const cAction = {
loginAction,
chapterAction,
examAction,
courseAction,
discussAction,
msgAction,
......
......@@ -4,6 +4,7 @@ import './components/style/_com.scss' // 定义 element-ui主题色 + 公共样
import VueRouter from 'vue-router' // 使用 vue-router
import createRouter from './router' // router定义
import Main from './main.vue' // 初始化 vue页面
import UploadForm from '../components/upload-form'
// import cTool from '@tools'
/* 引入 md5 */
import md5 from 'js-md5'
......@@ -18,6 +19,8 @@ require('promise.prototype.finally').shim()
Vue.use(Element)
Vue.use(VueRouter)
Vue.use(UploadForm)
Vue.component(UploadForm.name, UploadForm)
const router = createRouter()
/* 设置全局变量 */
window.G = Vue.prototype.$GlobalVariable = {
......
......@@ -120,6 +120,12 @@ export default [
name: 'courseWork',
component: () => import('../../components/player/courseWork/courseWork.vue'),
props: true
},
{
path: 'exam/:id',
name: 'exam',
component: () => import('../../components/player/exam/exam.vue'),
props: true
}
]
},
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论