提交 5a1f83ee authored 作者: 王鹏飞's avatar 王鹏飞

update viewer module

上级 8fd0e857
...@@ -88,3 +88,27 @@ export function updateCourseWork(semesterId, courseId, data) { ...@@ -88,3 +88,27 @@ export function updateCourseWork(semesterId, courseId, data) {
{ headers: { 'Content-Type': 'multipart/form-data' } } { headers: { 'Content-Type': 'multipart/form-data' } }
) )
} }
/**
* 获取课程考试详情
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
*/
export function getCourseExam(semesterId, courseId) {
return httpRequest.get(
`/v2/education/${semesterId}/${courseId}/examination`
)
}
/**
* 提交课程考试
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
*/
export function updateCourseExam(semesterId, courseId, data) {
return httpRequest.post(
`/v2/education/${semesterId}/${courseId}/essay`,
data,
{ headers: { 'Content-Type': 'multipart/form-data' } }
)
}
<template>
<div class="course-viewer-content">
<div class="course-viewer-content-hd">
<slot name="header">
<h3 class="course-viewer-content-hd__title">
<slot name="title">{{title}}</slot>
</h3>
<div class="course-viewer-content-hd__aside">
<slot name="header-aside"></slot>
</div>
</slot>
</div>
<div class="course-viewer-content-bd">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Continaer',
props: { title: String }
}
</script>
<template> <template>
<div class="course-viewer-content"> <div>
<div class="course-viewer-content-hd"> <ul class="file-list" v-if="files.length">
<h3 class="course-viewer-content-hd__title">{{title}}</h3> <li class="file-list-item" v-for="file in files" :key="file.id">
</div> <a :href="file.file_url" target="_blank">
<div class="course-viewer-content-bd"> <i class="el-icon-document"></i>
<ul class="file-list" v-if="files.length"> {{ file.file_name }}
<li class="file-list-item" v-for="file in files" :key="file.id"> </a>
<a :href="file.file_url" target="_blank"> <span v-if="file.file_size">{{ file.file_size }}</span>
<i class="el-icon-document"></i> <a :href="file.file_url" :download="file.file_name" target="_blank">
{{ file.file_name }} <el-tooltip effect="dark" content="下载">
</a> <i class="el-icon-download"></i>
<span v-if="file.file_size">{{ file.file_size }}</span> </el-tooltip>
<a :href="file.file_url" :download="file.file_name" target="_blank"> </a>
<el-tooltip effect="dark" content="下载"> </li>
<i class="el-icon-download"></i> </ul>
</el-tooltip> <div class="empty" v-else>
</a> <slot name="empty">暂无课程资料</slot>
</li>
</ul>
<div class="empty" v-else>
<slot name="empty">暂无课程资料</slot>
</div>
</div> </div>
</div> </div>
</template> </template>
......
...@@ -10,22 +10,37 @@ ...@@ -10,22 +10,37 @@
</template> </template>
<script> <script>
import VPlayer from './player/index.vue' // components
import VWork from './work/index.vue' import ChapterPlayer from '../src/player/ChapterPlayer.vue' // 章节视频
import VFile from './file/index.vue' import ChapterWork from '../src/work/index.vue' // 章节作业
import ChapterRead from '../src/read/chapterRead.vue' // 章节资料
import CourseWork from '../src/work/courseWork.vue' // 课程大作业
import CourseRead from '../src/read/courseRead.vue' // 课程资料
import CourseExam from '../src/work/courseExam.vue' // 课程资料
export default { export default {
name: 'ViewerLayout', name: 'ViewerLayout',
components: { VPlayer, VWork, VFile }, components: {
ChapterPlayer,
ChapterWork,
ChapterRead,
CourseWork,
CourseRead,
CourseExam
},
props: { props: {
chapter: { type: Object, default: () => {} } chapter: { type: Object, default: () => {} }
}, },
computed: { computed: {
currentCompoent() { currentCompoent() {
const componentNames = { const componentNames = {
2: 'VPlayer', // 视频 2: 'ChapterPlayer', // 视频
3: 'VWork', // 作业 3: 'ChapterWork', // 作业
4: 'VFile' // 资料 4: 'ChapterRead', // 资料
5: 'ChapterLive', // 直播
99: 'CourseWork', // 课程大作业
100: 'CourseRead', // 课程资料
101: 'CourseExam' // 课程考试
} }
return this.chapter ? componentNames[this.chapter.type] || '' : '' return this.chapter ? componentNames[this.chapter.type] || '' : ''
}, },
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
</template> </template>
<script> <script>
import * as api from '../../api/index' import * as api from '../api/index'
export default { export default {
name: 'VUpload', name: 'VUpload',
......
<template>
<div></div>
</template>
<script>
export default {
name: 'ChapterWork'
}
</script>
<style lang="scss" scoped>
</style>
...@@ -7,23 +7,25 @@ ...@@ -7,23 +7,25 @@
<i class="el-icon-arrow-left"></i> <i class="el-icon-arrow-left"></i>
</router-link> </router-link>
<h1 class="course-viewer-main-hd__title">{{ detail.course_name }}</h1> <h1 class="course-viewer-main-hd__title">{{ detail.course_name }}</h1>
<router-link to="/app/account/feedbackCreate" target="_blank"> <!-- 直播的时候显示帮助按钮 -->
<el-tooltip effect="light" content="意见反馈"> <template v-if="isLive">
<i class="el-icon-self-fankuiyijian"></i> <router-link to="/app/account/feedbackCreate" target="_blank">
</el-tooltip> <el-tooltip effect="light" content="意见反馈">
</router-link> <i class="el-icon-self-fankuiyijian"></i>
<router-link to="/mobile/help/student" target="_blank"> </el-tooltip>
<el-tooltip effect="light" content="帮助"> </router-link>
<i class="el-icon-self-icon-test"></i> <router-link to="/mobile/help/student" target="_blank">
</el-tooltip> <el-tooltip effect="light" content="帮助">
</router-link> <i class="el-icon-self-icon-test"></i>
</el-tooltip>
</router-link>
</template>
</div> </div>
<!-- 主体区域 --> <!-- 主体区域 -->
<div class="course-viewer-main-bd"> <div class="course-viewer-main-bd">
<router-view <router-view
:data="detail" :data="detail"
:chapter="activeChapter" :chapter="activeChapter"
:files="files"
:pptIndex="pptIndex" :pptIndex="pptIndex"
:key="pid" :key="pid"
@pptupdate="handlePPTupdate" @pptupdate="handlePPTupdate"
...@@ -39,6 +41,7 @@ ...@@ -39,6 +41,7 @@
:pptIndex="pptIndex" :pptIndex="pptIndex"
@change-ppt="handleChangePPT" @change-ppt="handleChangePPT"
v-if="detail.chapters" v-if="detail.chapters"
v-show="!isLive && !isCourseExam"
></v-aside> ></v-aside>
</div> </div>
</template> </template>
...@@ -81,14 +84,14 @@ export default { ...@@ -81,14 +84,14 @@ export default {
// 章节列表 // 章节列表
chapters() { chapters() {
const chapters = this.detail.chapters || [] const chapters = this.detail.chapters || []
return chapters.concat([ return chapters.concat([
{ {
name: '大作业及资料', name: '大作业及资料',
children: [ children: [
{ name: '课程大作业', id: 'course_work', type: 3 }, { name: '课程大作业', id: 'course_work', type: 99 },
{ name: '课程资料', id: 'course_info', type: 4 }, { name: '课程资料', id: 'course_info', type: 100 },
{ name: '教学评估', id: 'teach_evaluation' } { name: '教学评估', id: 'teach_evaluation', type: 102 },
{ name: '课程考试', id: 'course_exam', type: 101 }
] ]
} }
]) ])
...@@ -99,9 +102,13 @@ export default { ...@@ -99,9 +102,13 @@ export default {
const list = this.chapters const list = this.chapters
return this.findChapter(id, list) return this.findChapter(id, list)
}, },
// 课程资料 // 直播
files() { isLive() {
return this.detail.files || [] return this.activeChapter ? this.activeChapter.type === 5 : false
},
// 课程考试
isCourseExam() {
return this.activeChapter ? this.activeChapter.type === 101 : false
} }
}, },
methods: { methods: {
......
<template></template>
<script>
// 章节视频
export default {
name: 'ChapterPlayer'
}
</script>
...@@ -35,8 +35,9 @@ import * as api from '../../api/index' ...@@ -35,8 +35,9 @@ import * as api from '../../api/index'
// components // components
import videoPlayer from './videoPlayer.vue' import videoPlayer from './videoPlayer.vue'
import pptPlayer from './pptPlayer.vue' import pptPlayer from './pptPlayer.vue'
export default { export default {
name: 'ViewerPlayer', name: 'ChapterPlayer',
components: { videoPlayer, pptPlayer }, components: { videoPlayer, pptPlayer },
props: { props: {
// 当前章节 // 当前章节
......
<template> <template>
<file-panel :title="chapter.name" :files="files"></file-panel> <container :title="chapter.name">
<file-list :files="files"></file-list>
</container>
</template> </template>
<script> <script>
import FilePanel from './filePanel.vue' // components
import Container from '../../components/container.vue'
import FileList from '../../components/fileList.vue'
// 章节阅读资料
export default { export default {
name: 'ViewerFile', name: 'ChapterRead',
components: { FilePanel }, components: { Container, FileList },
props: { props: {
// 当前选中的 // 当前选中的
chapter: { type: Object, default: () => {} }, chapter: { type: Object, default: () => {} },
...@@ -15,20 +22,12 @@ export default { ...@@ -15,20 +22,12 @@ export default {
}, },
computed: { computed: {
files() { files() {
// 课程资料 const reading = this.chapter.reading
if (this.chapter.id === 'course_info') { const file = {
return this.data.files || [] file_name: reading.reading_content,
} file_url: reading.reading_attachment
// 章节资料
if (this.chapter.reading) {
const reading = this.chapter.reading
const file = {
file_name: reading.reading_content,
file_url: reading.reading_attachment
}
return [file]
} }
return [] return [file]
} }
} }
} }
......
<template>
<container :title="chapter.name">
<file-list :files="files"></file-list>
</container>
</template>
<script>
// components
import Container from '../../components/container.vue'
import FileList from '../../components/fileList.vue'
// 课程阅读资料
export default {
name: 'CourseRead',
components: { Container, FileList },
props: {
// 当前选中的
chapter: { type: Object, default: () => {} },
// 课程详情接口返回的数据
data: { type: Object, default: () => {} }
},
computed: {
files() {
return this.data.files || []
}
}
}
</script>
<template>
<div>
<ul class="file-list" v-if="files.length">
<li class="file-list-item" v-for="file in files" :key="file.id">
<a :href="file.file_url" target="_blank">
<i class="el-icon-document"></i>
{{ file.file_name }}
</a>
<span v-if="file.file_size">{{ file.file_size }}</span>
<a :href="file.file_url" :download="file.file_name" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
</el-tooltip>
</a>
</li>
</ul>
<div class="empty" v-else>
<slot name="empty">暂无课程资料</slot>
</div>
</div>
</template>
<script>
export default {
name: 'FilePanel',
props: {
// 标题
title: { type: String, default: '课程资料' },
// 文件列表
files: { type: Array, default: () => [] }
}
}
</script>
<style lang="scss" scoped>
.file-list {
padding: 0;
}
.file-list-item {
display: flex;
font-size: 16px;
padding: 20px 30px;
margin-bottom: 10px;
background-color: #fff;
list-style: none;
border-radius: 32px;
justify-content: space-between;
a {
text-decoration: none;
color: #333;
&:hover {
color: #b49441;
}
}
}
.empty {
font-size: 18px;
line-height: 80px;
background-color: #fff;
text-align: center;
border-radius: 40px;
}
</style>
<template> <template>
<div class="course-viewer-content" v-loading="loading"> <container :title="chapter.name" v-loading="loading">
<div class="course-viewer-content-hd"> <div class="exam">
<h3 class="course-viewer-content-hd__title">{{chapter.name}}</h3> <div class="exam-form">
<div class="course-viewer-content-hd__aside" v-if="isSubmited">正确率:{{detail.score}}%</div> <el-form :disabled="isSubmited" size="medium">
</div> <chapter-exam-item
<div class="course-viewer-content-bd"> v-for="(item, index) in unorderedQuestions"
<div class="exam"> :disabled="isSubmited"
<div class="exam-form"> :data="item"
<el-form :disabled="isSubmited" size="medium"> :value="findAnswerById(item.id)"
<chapter-exam-item :index="index"
v-for="(item, index) in unorderedQuestions" :key="item.id"
:disabled="isSubmited" @change="onChange(item.id, ...arguments)"
:data="item" ></chapter-exam-item>
:value="findAnswerById(item.id)" <div class="exam-buttons">
:index="index" <el-tooltip effect="dark" content="提交之后就不能修改了哦" placement="right">
:key="item.id" <el-button type="primary" @click="onSubmit">{{submitText}}</el-button>
@change="onChange(item.id, ...arguments)" </el-tooltip>
></chapter-exam-item> </div>
<div class="exam-buttons"> </el-form>
<el-tooltip effect="dark" content="提交之后就不能修改了哦" placement="right">
<el-button type="primary" @click="onSubmit">{{submitText}}</el-button>
</el-tooltip>
</div>
</el-form>
</div>
</div> </div>
</div> </div>
</div> </container>
</template> </template>
<script> <script>
// libs // libs
import { shuffle } from 'lodash' import { shuffle } from 'lodash'
// components // components
import Container from '../../components/container.vue'
import ChapterExamItem from './chapterExamItem.vue' import ChapterExamItem from './chapterExamItem.vue'
// api // api
import * as api from '../../api/index' import * as api from '../../api/index'
// 章节测试题
export default { export default {
name: 'ChapterExam', name: 'ChapterExam',
components: { ChapterExamItem }, components: { Container, ChapterExamItem },
props: { props: {
// 当前选中的章节 // 当前选中的章节
chapter: { type: Object, default: () => {} } chapter: { type: Object, default: () => {} }
...@@ -107,7 +104,7 @@ export default { ...@@ -107,7 +104,7 @@ export default {
}, },
methods: { methods: {
// 获取测试答题详情 // 获取测试答题详情
getChapterExam() { getDetail() {
this.loading = true this.loading = true
api api
.getChapterExam(this.sid, this.cid, this.resourceId) .getChapterExam(this.sid, this.cid, this.resourceId)
...@@ -207,7 +204,7 @@ export default { ...@@ -207,7 +204,7 @@ export default {
handleSubmitRequest(params) { handleSubmitRequest(params) {
api.sbumitChapterExam(params).then(response => { api.sbumitChapterExam(params).then(response => {
if (response.status) { if (response.status) {
this.getChapterExam() this.getDetail()
} else { } else {
this.$message.error(response.data.error) this.$message.error(response.data.error)
} }
...@@ -215,7 +212,7 @@ export default { ...@@ -215,7 +212,7 @@ export default {
} }
}, },
beforeMount() { beforeMount() {
this.getChapterExam() this.getDetail()
} }
} }
</script> </script>
......
<template>
<container :title="chapter.name" v-loading="loading">
<chapter-work-item
v-for="(item, index) in questions"
:disabled="isSubmited"
:data="item"
:index="index"
:key="item.id"
@change="onChange(item.id, ...arguments)"
></chapter-work-item>
<div class="work-bottom" v-if="detail">
<div class="info">
<template v-if="isRevised">
<div class="paper-check">
<p>批改时间:{{detail.check_date}}</p>
<div class="paper-check-item">
<b>评分:</b>
{{detail.score}}
</div>
<div class="paper-check-item">
<b>评语:</b>
<div class="edit_html" v-html="detail.check_comments"></div>
</div>
</div>
</template>
<template v-else-if="detail.created_time">
<p class="help">已于 {{detail.created_time}} 提交,等待老师批改中。</p>
<template v-if="detail.updated_time !== detail.created_time">
<p class="help">最近提交时间: {{detail.updated_time}}</p>
</template>
</template>
</div>
</div>
<div class="buttons">
<el-tooltip content="在获老师批改之前,可以多次提交,将以最后一次提交为准" placement="right">
<el-button type="primary" @click="onSubmit" :disabled="isRevised">{{submitText}}</el-button>
</el-tooltip>
</div>
</container>
</template>
<script>
// componets
import Container from '../../components/container.vue'
import ChapterWorkItem from './chapterWorkItem.vue'
// api
import * as api from '../../api/index'
// 章节作业
export default {
name: 'ChapterWork',
components: { Container, ChapterWorkItem },
props: {
// 当前选中的
chapter: { type: Object, default: () => {} },
// 课程详情接口返回的数据
data: { type: Object, default: () => {} }
},
data() {
return {
ruleForm: {
essay_name: '',
essay_description: '',
url: ''
},
rules: {
essay_name: [
{ required: true, message: '请输入主题', trigger: 'blur' },
{ max: 5, message: '最多输入 50 个字符', trigger: 'blur' }
],
essay_description: [
{ required: true, message: '请输入正文', trigger: 'blur' }
],
url: [{ required: true, message: '请上传附件', trigger: 'change' }]
},
detail: null,
loading: false,
messageInstance: null
}
},
computed: {
// 学期ID
sid() {
return '6552021107166150656'
},
// 课程ID
cid() {
return '6568035374902280192'
},
// 当前页面的ID
pid() {
return this.$route.params.id
},
// 资源ID
resourceId() {
return this.chapter.resource_id
},
// 问题列表
questions() {
const homework = this.chapter.homework
return homework ? homework.questions : []
},
// 是否批改
isRevised() {
return this.detail ? !!this.detail.check_date : false
},
// 提交按钮文本
submitText() {
return this.isRevised ? '已批改' : '提交'
}
},
methods: {
// 获取详情
getDetail() {
this.loading = true
api
.getChapterExam(this.sid, this.cid, this.resourceId)
.then(response => {
this.detail = Array.isArray(response) ? null : response
if (this.detail) {
this.ruleForm.essay_name = this.detail.essay_name
this.ruleForm.essay_description = this.detail.essay_description
this.ruleForm.url = this.detail.file_url
}
})
.finally(() => {
this.loading = false
})
},
// 提交
onSubmit() {
this.$refs.ruleForm
.validate()
.then(response => {
const params = Object.assign(this.ruleForm, {
semester_id: this.sid,
course_id: this.cid
})
this.handleSubmitRequest(params)
})
.catch(() => {
this.messageInstance && this.messageInstance.close()
this.messageInstance = this.$message.error('还有题目未做,不能提交')
})
},
// 请求提交接口
handleSubmitRequest(params) {
api
.sbumitChapterExam(params)
.then(response => {
if (response.status) {
this.$message.success('提交成功,等待批改')
this.getDetail()
} else {
this.$message.error(response.data.error)
}
})
.catch(error => {
this.$message.error(error.message)
})
}
},
beforeMount() {
this.getDetail()
}
}
</script>
<template>
<div class="q-item">
<div class="q-item-hd">
<div class="q-item-num">{{currentIndex}}.</div>
<div class="q-item-title" v-html="data.question_content"></div>
<div class="q-item-aside" v-if="currentTypeText">({{currentTypeText}})</div>
</div>
<div class="q-item-bd">
<!-- 单选 -->
<el-radio-group v-model="radioValue" @change="onRadioChange" v-if="currentType === 1">
<div class="q-option-item" v-for="item in currentOptions" :key="item.id">
<el-radio :class="genClass(item)" :label="item.id">{{item.abc_option}}</el-radio>
</div>
</el-radio-group>
<!-- 多选 -->
<el-checkbox-group
v-model="checkboxValue"
@change="onCheckboxChange"
v-if="currentType === 2"
>
<div class="q-option-item" v-for="item in currentOptions" :key="item.id">
<el-checkbox :class="genClass(item)" :label="item.id">{{item.abc_option}}</el-checkbox>
</div>
</el-checkbox-group>
<!-- 简答题 -->
<v-editor></v-editor>
<v-upload></v-upload>
</div>
<div class="q-item-ft" v-if="disabled">
<p>
<span>学生答案:</span>
<span :class="isCorrect ? 'is-success' : 'is-error'">{{submitAnswerText}}</span>
</p>
<p>
<span>正确答案:</span>
<span>{{correctAnswerText}}</span>
</p>
</div>
</div>
</template>
<script>
import VEditor from '../../components/editor.vue'
import VUpload from '../../components/upload.vue'
export default {
name: 'ChapterExamItem',
components: { VEditor, VUpload },
props: {
// 索引
index: { type: Number },
// 单条数据
data: { type: Object, default: () => {} },
// 提交的答案
value: { type: Array, default: () => [] },
// 是否禁用,提交过的是禁用状态
disabled: { type: Boolean, default: false }
},
data() {
return {
radioValue: '',
checkboxValue: []
}
},
watch: {
value: {
immediate: true,
handler(value) {
if (this.currentType === 1) {
this.radioValue = value[0] || ''
} else {
this.checkboxValue = value
}
}
}
},
computed: {
// 26个英文字母
A_Z() {
const result = []
for (let i = 0; i < 26; i++) {
result.push(String.fromCharCode(65 + i))
}
return result
},
// 序号
currentIndex() {
return this.index + 1
},
// 当前类型
currentType() {
return this.data.question_type
},
// 选项类型
currentTypeText() {
const map = { 1: '单选题', 2: '多选题' }
return map[this.currentType]
},
// 接口返回的options数据
options() {
return this.data.question_options
? JSON.parse(this.data.question_options)
: []
},
// 处理后的options数据
currentOptions() {
return this.options.map((item, index) => {
// 英文字母 + 名称
item.abc = this.A_Z[index]
item.abc_option = `${this.A_Z[index]}. ${item.option}`
// 提交时的选中状态
item.selected = this.value.includes(item.id)
return item
})
},
// 正确答案显示的英文字母
correctAnswerText() {
const result = this.currentOptions.reduce((result, item) => {
item.checked && result.push(item.abc)
return result
}, [])
return result.join('、')
},
// 提交答案显示的英文字母
submitAnswerText() {
const result = this.currentOptions.reduce((result, item) => {
item.selected && result.push(item.abc)
return result
}, [])
return result.join('、')
},
// 是否回答正确
isCorrect() {
const options = this.currentOptions
for (let i = 0; i < options.length; i++) {
if (options[i].checked !== !!options[i].selected) {
return false
}
}
return true
}
},
methods: {
// 生成class
genClass(item) {
if (!this.disabled) {
return null
}
return {
'is-error': item.selected !== item.checked,
'is-success': item.checked
}
},
// 单选
onRadioChange(value) {
this.$emit('change', [value])
},
// 多选
onCheckboxChange(value) {
this.$emit('change', value)
}
}
}
</script>
<style lang="scss" scoped>
.q-item {
font-size: 16px;
padding: 10px 0;
border-bottom: 1px solid #c9c9c97a;
}
.q-item-hd {
display: flex;
padding: 10px 0 20px;
::v-deep p {
margin: 0;
padding: 0;
}
}
.q-item-num {
width: 20px;
text-align: center;
}
.q-item-title {
flex: 1;
padding: 0 10px;
}
.q-item-aside {
padding-left: 20px;
// align-self: flex-end;
}
.q-option-item {
padding-left: 30px;
margin-bottom: 14px;
}
.is-success {
color: #090;
}
.is-error {
color: #d80000;
}
::v-deep .el-radio {
&.is-disabled .el-radio__label {
color: #3c3c3c;
}
&.is-error .el-radio__label {
color: #d80000;
}
&.is-success .el-radio__label {
color: #090;
}
}
::v-deep .el-checkbox {
&.is-disabled .el-checkbox__label {
color: #3c3c3c;
}
&.is-error .el-checkbox__label {
color: #d80000;
}
&.is-success .el-checkbox__label {
color: #090;
}
}
.q-item-ft {
display: flex;
justify-content: flex-end;
padding: 10px 0;
p {
font-size: 14px;
margin: 0;
padding-left: 20px;
}
}
</style>
<template>
<container :title="detail.title" v-loading="loading">
<div class="exam">
<div class="exam-form">
<el-form :disabled="isSubmited" size="medium">
<course-exam-item
v-for="(item, index) in questions"
:index="index"
:type="item.type"
:data="item"
:key="item.id"
></course-exam-item>
<div class="exam-buttons">
<el-tooltip effect="dark" content="提交之后就不能修改了哦" placement="right">
<el-button type="primary" @click="onSubmit">{{submitText}}</el-button>
</el-tooltip>
</div>
</el-form>
</div>
</div>
</container>
</template>
<script>
// components
import Container from '../../components/container.vue'
import CourseExamItem from './courseExamItem.vue'
// api
import * as api from '../../api/index'
// 章节测试题
export default {
name: 'CourseExam',
components: { Container, CourseExamItem },
props: {
// 当前选中的章节
chapter: { type: Object, default: () => {} }
},
data() {
return {
loading: false,
detail: {},
// questions: [],
values: [], // 提交的答案
startTime: new Date().getTime(), // 进入时间
messageInstance: null
}
},
computed: {
// 学期ID
sid() {
return '6552021107166150656'
},
// 课程ID
cid() {
return '6568035374902280192'
},
// 当前页面的ID
pid() {
return this.$route.params.id
},
// 问题列表
questions() {
if (!this.detail.examination) {
return []
}
const addKey = function(array, key, value) {
return array.map(item => {
item[key] = value
return item
})
}
let { radioList, checkboxList, shortAnswerList } = this.detail.examination
// 单选
radioList = addKey(radioList, 'type', 1)
// 多选
checkboxList = addKey(checkboxList, 'type', 2)
// 问答
shortAnswerList = addKey(shortAnswerList, 'type', 3)
return [...radioList, ...checkboxList, ...shortAnswerList]
},
// 是否提交
isSubmited() {
return this.detail ? !!this.detail.work_contents : false
},
// 提交按钮文本
submitText() {
return this.isSubmited ? '已提交' : '提交'
}
},
methods: {
// 获取测试答题详情
getDetail() {
this.loading = true
api
.getCourseExam(this.sid, this.cid)
.then(response => {
this.detail = Array.isArray(response) ? null : response
})
.finally(() => {
this.loading = false
})
},
// 通过问题ID查找答案
findAnswerById(id) {
const found = this.answers.find(item => item.question_id === id)
return found ? found.value : []
},
onChange(qid, value) {
const index = this.values.findIndex(item => item.question_id === qid)
if (index === -1) {
this.values.push({ question_id: qid, value })
} else {
this.values.splice(index, 1, { question_id: qid, value })
}
},
// 提交校验
checkSubmit() {
if (this.values.length !== this.questions.length) {
return false
}
const values = this.values
for (let i = 0; i < values.length; i++) {
const options = values[i].value
if (!options.length) {
return false
}
}
return true
},
// 提交
onSubmit() {
if (!this.checkSubmit()) {
this.messageInstance && this.messageInstance.close()
this.messageInstance = this.$message.error('还有题目未做,不能提交')
return
}
// 计算答题时间
const duration = Math.floor(
(new Date().getTime() - this.startTime) / 1000
)
// 答案数据
const data = this.handleSubmitData()
// 计算分数
const score = data.reduce((result, item) => {
item.is_correct && result++
return result
}, 0)
const total = this.questions.length
const params = {
semester_id: this.sid,
course_id: this.cid,
chapter_id: this.pid,
work_id: this.resourceId,
work_contents: JSON.stringify(data),
duration,
score: ((score / total) * 100).toFixed(1)
}
// 请求接口
this.handleSubmitRequest(params)
},
// 提交的答案数据
handleSubmitData() {
const result = this.questions.map(item => {
// 查找提交的option id
const found = this.values.find(
subitem => subitem.question_id === item.id
)
const ids = found ? found.value : []
// 解析
const parseOptions = JSON.parse(item.question_options)
// 设置提交选中状态
let isCorrect = true
const options = parseOptions.map(option => {
option.selected = ids.includes(option.id) ? 1 : 0
if (option.checked !== !!option.selected && isCorrect) {
isCorrect = false
}
return option
})
return {
question_id: item.id,
is_correct: isCorrect ? 1 : 0,
options
}
})
return result
},
// 请求提交接口
handleSubmitRequest(params) {
api.sbumitChapterExam(params).then(response => {
if (response.status) {
this.getDetail()
} else {
this.$message.error(response.data.error)
}
})
}
},
beforeMount() {
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.exam-buttons {
padding: 40px 0;
text-align: center;
.el-button {
width: 240px;
margin: 40px auto;
}
}
</style>
<template>
<div class="q-item">
<div class="q-item-hd">
<div class="q-item-num">{{currentIndex}}.</div>
<div class="q-item-title" v-html="data.content">{{data.title}}</div>
<div class="q-item-aside" v-if="currentTypeText">({{currentTypeText}})</div>
</div>
<div class="q-item-bd">
<!-- 单选 -->
<el-radio-group v-model="radioValue" @change="onRadioChange" v-if="currentType === 1">
<div class="q-option-item" v-for="item in currentOptions" :key="item.id">
<el-radio :class="genClass(item)" :label="item.id">{{item.abc_option}}</el-radio>
</div>
</el-radio-group>
<!-- 多选 -->
<el-checkbox-group
v-model="checkboxValue"
@change="onCheckboxChange"
v-if="currentType === 2"
>
<div class="q-option-item" v-for="item in currentOptions" :key="item.id">
<el-checkbox :class="genClass(item)" :label="item.id">{{item.abc_option}}</el-checkbox>
</div>
</el-checkbox-group>
<!-- 简答题 -->
<template v-if="currentType === 3">
<v-editor></v-editor>
<v-upload></v-upload>
</template>
</div>
</div>
</template>
<script>
// components
import VEditor from '../../components/editor.vue'
import VUpload from '../../components/upload.vue'
export default {
name: 'CourseExamItem',
components: { VEditor, VUpload },
props: {
// 索引
index: { type: Number },
// 问题类型
type: { type: Number },
// 单条数据
data: { type: Object, default: () => {} },
// 提交的答案
value: { type: Array, default: () => [] },
// 是否禁用,提交过的是禁用状态
disabled: { type: Boolean, default: false }
},
data() {
return {
radioValue: '',
checkboxValue: []
}
},
watch: {
value: {
immediate: true,
handler(value) {
if (this.currentType === 1) {
this.radioValue = value[0] || ''
} else {
this.checkboxValue = value
}
}
}
},
computed: {
// 26个英文字母
A_Z() {
const result = []
for (let i = 0; i < 26; i++) {
result.push(String.fromCharCode(65 + i))
}
return result
},
// 序号
currentIndex() {
return this.index + 1
},
// 当前类型
currentType() {
return this.type || this.data.question_type
},
// 选项类型
currentTypeText() {
const map = { 1: '单选题', 2: '多选题' }
return map[this.currentType]
},
// 处理后的options数据
currentOptions() {
return this.data.options.map((item, index) => {
// 英文字母 + 名称
item.abc = this.A_Z[index]
item.abc_option = `${this.A_Z[index]}. ${item.option}`
// 提交时的选中状态
item.selected = this.value.includes(item.id)
return item
})
}
},
methods: {
// 生成class
genClass(item) {
if (!this.disabled) {
return null
}
return {
'is-error': item.selected !== item.checked,
'is-success': item.checked
}
},
// 单选
onRadioChange(value) {
this.$emit('change', [value])
},
// 多选
onCheckboxChange(value) {
this.$emit('change', value)
}
}
}
</script>
<style lang="scss" scoped>
.q-item {
font-size: 16px;
padding: 10px 0;
border-bottom: 1px solid #c9c9c97a;
}
.q-item-hd {
display: flex;
padding: 10px 0 20px;
::v-deep p {
margin: 0;
padding: 0;
}
}
.q-item-num {
width: 20px;
text-align: center;
}
.q-item-title {
flex: 1;
padding: 0 10px;
}
.q-item-aside {
padding-left: 20px;
// align-self: flex-end;
}
.q-option-item {
padding-left: 30px;
margin-bottom: 14px;
}
.is-success {
color: #090;
}
.is-error {
color: #d80000;
}
::v-deep .el-radio {
&.is-disabled .el-radio__label {
color: #3c3c3c;
}
&.is-error .el-radio__label {
color: #d80000;
}
&.is-success .el-radio__label {
color: #090;
}
}
::v-deep .el-checkbox {
&.is-disabled .el-checkbox__label {
color: #3c3c3c;
}
&.is-error .el-checkbox__label {
color: #d80000;
}
&.is-success .el-checkbox__label {
color: #090;
}
}
.q-item-ft {
display: flex;
justify-content: flex-end;
padding: 10px 0;
p {
font-size: 14px;
margin: 0;
padding-left: 20px;
}
}
</style>
<template> <template>
<div class="course-viewer-content" v-loading="loading"> <container :title="chapter.name" v-loading="loading">
<div class="course-viewer-content-hd"> <el-steps direction="vertical" v-if="data.curriculum">
<h3 class="course-viewer-content-hd__title">课程大作业</h3> <el-step title="阅读大作业要求" status="process">
</div> <template v-slot:description>
<div class="course-viewer-content-bd"> <div v-html="data.curriculum.curriculum_essay"></div>
<el-steps direction="vertical" v-if="data.curriculum"> <p>截止日期:{{data.essay_date}}</p>
<el-step title="阅读大作业要求" status="process"> </template>
<template v-slot:description> </el-step>
<div v-html="data.curriculum.curriculum_essay"></div> <el-step title="填写作业主题、正文,上传附件(点击“提交”保存)" status="process">
<p>截止日期:{{data.essay_date}}</p> <template v-slot:description>
</template> <el-form
</el-step> :model="ruleForm"
<el-step title="填写作业主题、正文,上传附件(点击“提交”保存)" status="process"> :rules="rules"
<template v-slot:description> :hide-required-asterisk="true"
<el-form :disabled="isRevised"
:model="ruleForm" label-position="top"
:rules="rules" ref="ruleForm"
:hide-required-asterisk="true" >
:disabled="isRevised" <el-form-item label="主题" prop="essay_name">
label-position="top" <el-input v-model="ruleForm.essay_name" placeholder="主题"></el-input>
ref="ruleForm" </el-form-item>
> <el-form-item label="正文" prop="essay_description">
<el-form-item label="主题" prop="essay_name"> <!-- 编辑器 -->
<el-input v-model="ruleForm.essay_name" placeholder="主题"></el-input> <v-editor :disabled="isRevised" v-model="ruleForm.essay_description"></v-editor>
</el-form-item> </el-form-item>
<el-form-item label="正文" prop="essay_description"> <el-form-item label="附件" prop="url">
<!-- 编辑器 --> <!-- 文件上传 -->
<v-editor :disabled="isRevised" v-model="ruleForm.essay_description"></v-editor> <v-upload v-model="ruleForm.url">
</el-form-item> <!-- <template v-slot:tip>只支持docx格式的文件,文件小于10M</template> -->
<el-form-item label="附件" prop="url"> </v-upload>
<!-- 文件上传 --> </el-form-item>
<v-upload v-model="ruleForm.url"> </el-form>
<template v-slot:tip>只支持docx格式的文件,文件小于10M</template> </template>
</v-upload> </el-step>
</el-form-item> <el-step title="截止日期前提交" status="process">
</el-form> <template v-slot:description>
</template> <div class="work-bottom" v-if="detail">
</el-step> <div class="info">
<el-step title="截止日期前提交" status="process"> <template v-if="isRevised">
<template v-slot:description> <div class="paper-check">
<div class="work-bottom" v-if="detail"> <p>批改时间:{{detail.check_date}}</p>
<div class="info"> <div class="paper-check-item">
<template v-if="isRevised"> <b>评分:</b>
<div class="paper-check"> {{detail.score}}
<p>批改时间:{{detail.check_date}}</p>
<div class="paper-check-item">
<b>评分:</b>
{{detail.score}}
</div>
<div class="paper-check-item">
<b>评语:</b>
<div class="edit_html" v-html="detail.check_comments"></div>
</div>
</div> </div>
<div class="paper-check-item">
<b>评语:</b>
<div class="edit_html" v-html="detail.check_comments"></div>
</div>
</div>
</template>
<template v-else-if="detail.created_time">
<p class="help">已于 {{detail.created_time}} 提交,等待老师批改中。</p>
<template v-if="detail.updated_time !== detail.created_time">
<p class="help">最近提交时间: {{detail.updated_time}}</p>
</template> </template>
<template v-else-if="detail.created_time"> </template>
<p class="help">已于 {{detail.created_time}} 提交,等待老师批改中。</p>
<template v-if="detail.updated_time !== detail.created_time">
<p class="help">最近提交时间: {{detail.updated_time}}</p>
</template>
</template>
</div>
</div> </div>
<div class="buttons"> </div>
<el-tooltip content="在获老师批改之前,可以多次提交,将以最后一次提交为准" placement="right"> <div class="buttons">
<el-button type="primary" @click="onSubmit" :disabled="isRevised">{{submitText}}</el-button> <el-tooltip content="在获老师批改之前,可以多次提交,将以最后一次提交为准" placement="right">
</el-tooltip> <el-button type="primary" @click="onSubmit" :disabled="isRevised">{{submitText}}</el-button>
</div> </el-tooltip>
</template> </div>
</el-step> </template>
</el-steps> </el-step>
</div> </el-steps>
</div> </container>
</template> </template>
<script> <script>
// componetns // componets
import VEditor from './editor.vue' import Container from '../../components/container.vue'
import VUpload from './upload.vue' import VEditor from '../../components/editor.vue'
import VUpload from '../../components/upload.vue'
// api // api
import * as api from '../../api/index' import * as api from '../../api/index'
// 课程大作业
export default { export default {
name: 'CourseWork', name: 'CourseWork',
components: { VEditor, VUpload }, components: { Container, VEditor, VUpload },
props: { props: {
// 当前选中的
chapter: { type: Object, default: () => {} },
// 课程详情接口返回的数据 // 课程详情接口返回的数据
data: { type: Object, default: () => {} } data: { type: Object, default: () => {} }
}, },
......
...@@ -10,12 +10,13 @@ ...@@ -10,12 +10,13 @@
</template> </template>
<script> <script>
import CourseWork from './courseWork.vue' // componets
import ChapterWork from './chapterWork.vue'
import ChapterExam from './chapterExam.vue' import ChapterExam from './chapterExam.vue'
export default { export default {
name: 'ViewerWork', name: 'ViewerWork',
components: { CourseWork, ChapterExam }, components: { ChapterWork, ChapterExam },
props: { props: {
// 当前选中的 // 当前选中的
chapter: { type: Object, default: () => {} }, chapter: { type: Object, default: () => {} },
...@@ -26,10 +27,10 @@ export default { ...@@ -26,10 +27,10 @@ export default {
currentCompoent() { currentCompoent() {
const componentNames = { const componentNames = {
1: 'ChapterExam', // 考试 1: 'ChapterExam', // 考试
2: 'CourseWork' // 作业 2: 'ChapterWork' // 作业
} }
const homework = this.chapter.homework const homework = this.chapter.homework
return homework ? componentNames[homework.work_type] || '' : 'CourseWork' return homework ? componentNames[homework.work_type] : ''
} }
} }
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论