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

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

上级 8a3a33b7
差异被折叠。
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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论