Commit 04aa2e85 by tangyi

Merge branch 'dev'

# Conflicts: # README.md
parents 2d6b0049 56bc7c05
Version v3.5.0 (2020-01-19)
--------------------------
改进:
* 支持不定项选项
* 多处优化
Version v3.5.0 (2019-12-08)
--------------------------
......
# 1 简介
<h1 align="center">Welcome to spring-microservice-exam 👋</h1>
<p>
<img alt="Version" src="https://img.shields.io/badge/version-3.5.0-blue.svg?cacheSeconds=2592000" />
<a href="https://www.kancloud.cn/tangyi/spring-microservice-exam/1322864" target="_blank">
<img alt="Documentation" src="https://img.shields.io/badge/documentation-yes-brightgreen.svg" />
</a>
<a href="#" target="_blank">
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" />
</a>
</p>
硕果云,基于Spring Cloud搭建的新一代微服务教学管理平台,提供多租户、权限管理、考试、练习等功能。
> 硕果云,基于Spring Cloud搭建的新一代微服务教学管理平台,提供多租户、权限管理、考试、练习等功能。
# 2 在线体验
### 🏠 [主页](https://gitee.com/wells2333/spring-microservice-exam)
- 前台:[http://118.25.138.130](http://118.25.138.130)
### ✨ [在线体验-前台](http://118.25.138.130)
- 后台:[http://118.25.138.130:81](http://118.25.138.130:81)
### ✨ [在线体验-后台](http://118.25.138.130:81)
账号:
测试账号:
| 单位ID | 账号 | 密码 | 角色 |
| --------- | -------- | -------- | -------- |
......@@ -16,7 +25,7 @@
| gitee | student | 123456 | 学生 |
| gitee | teacher | 123456 | 老师 |
# 3 技术选型
## 技术选型
- 服务注册与发现:`Consul`
- 熔断器:`Hystrix` + `Turbine`
......@@ -32,22 +41,22 @@
- 构建工具:`Maven`
- 后台 API 文档:`Swagger`
- 消息队列:`RabbitMQ`
- 文件系统:`FastDFS`
- 文件系统:`七牛云`
- 缓存:`Redis`
- 前端:`vue`
# 4 核心依赖
## 核心依赖
| 名称 | 版本 |
| --------- | -------- |
| `Spring Boot` | `2.1.11.RELEASE` |
| `Spring Cloud` | `Greenwich.SR4` |
# 5 系统架构
## 系统架构
![image](docs/images/系统架构图v3.0.jpg)
# 6 功能概述
## 功能概述
项目分前台网站和后台管理两部分,前台主要提供考试功能,后台提供基础管理、考试管理功能。
......@@ -84,9 +93,9 @@
- 个人资料:姓名、头像等基本信息的修改
- 修改密码:修改密码
# 7 功能演示
## 系统截图
## 7.1 前台功能
### 前台功能
1. 首页
![image](docs/images/image_web.png)
......@@ -94,10 +103,7 @@
2. 考试
![image](docs/images/image_web_exam.png)
3. 查看错题
![image](docs/images/image_web_incorrect_answer.png)
## 7.2 后台功能
### 后台功能
1. 总体功能
![image](docs/images/image_ui_menu.png)
......@@ -108,16 +114,7 @@
3. 题目管理
![image](docs/images/image_ui_subjects_rich_edit.png)
4. 个人资料
![image](docs/images/image_ui_msg.png)
# 8 问题反馈
欢迎提交 issue,请写清楚遇到问题的原因、浏览器、操作系统环境、重现的流程和报错日志等。
如果有开发能力,建议在本地调试出出错的代码。
# 9 参考资料
## 部署文档
- [在线考试系统-部署文档](https://www.kancloud.cn/tangyi/spring-microservice-exam/1322870)
......@@ -129,20 +126,28 @@
- [在线考试系统V3.0镜像构建、推送、部署](http://ehedgehog.net/2019/04/22/%E5%9C%A8%E7%BA%BF%E8%80%83%E8%AF%95%E7%B3%BB%E7%BB%9FV2.0%E9%95%9C%E5%83%8F%E6%9E%84%E5%BB%BA%E3%80%81%E6%8E%A8%E9%80%81%E3%80%81%E9%83%A8%E7%BD%B2/)
***
## 作者
👤 **tangyi**
* Gitee: [@wells2333](https://gitee.com/wells2333)
# 10 关于
* Github: [@wells2333](https://github.com/wells2333)
交流QQ群:<a target="_blank" href="https://jq.qq.com/?_wv=1027&k=5RKZNF2"><img border="0" src="http://pub.idqqimg.com/wpa/images/group.png" alt="Spring Cloud考试系统学习" title="Spring Cloud考试系统学习"></a>
## 🤝 参与贡献
QQ群号:
欢迎提交PR、[issues](https://gitee.com/wells2333/spring-microservice-exam/issues)一起完善项目
996208878(已满)
## 反馈交流
<img src="http://118.25.138.130/static/img/WechatIMG4.c775d3e.png" alt="Spring Cloud考试系统学习" title="Spring Cloud考试系统学习">
交流QQ群:
346164822(新群)
![image](docs/images/qq.png) ![image](docs/images/qq_new.png)
## 请作者喝咖啡
<img src="http://q2at6vru5.bkt.clouddn.com/WechatIMG2.png" alt="Spring Cloud考试系统学习" title="Spring Cloud考试系统学习">
如果您觉得有帮助,请点右上角 ⭐️ "Star" 或者**微信扫一扫**支持一下,谢谢!
如果您觉得有帮助,请点右上角 "Star" 或者项目底部的“捐助”支持一下,谢谢!
\ No newline at end of file
![image](docs/images/wechat.png)
***
......@@ -60,10 +60,10 @@ export function delAllSubject (obj) {
// 导出
export function exportSubject (ids, examinationId, categoryId) {
let url = baseSubjectUrl + 'export?'
if (examinationId !== null && examinationId !== '') {
if (examinationId !== undefined && examinationId !== null && examinationId !== '') {
url = url + 'examinationId=' + examinationId
}
if (categoryId !== null && categoryId !== '') {
if (categoryId !== undefined && categoryId !== null && categoryId !== '') {
url = url + '&categoryId=' + categoryId
}
return request({
......
......@@ -24,57 +24,55 @@
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.optionA')">
<el-input v-model="subjectInfo.options[0].optionContent" @focus="updateTinymceContent(subjectInfo.options[0].optionContent, tinymceEdit.optionA)"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.optionB')">
<el-input v-model="subjectInfo.options[1].optionContent" @focus="updateTinymceContent(subjectInfo.options[1].optionContent, tinymceEdit.optionB)"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.optionC')">
<el-input v-model="subjectInfo.options[2].optionContent" @focus="updateTinymceContent(subjectInfo.options[2].optionContent, tinymceEdit.optionC)"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.optionD')">
<el-input v-model="subjectInfo.options[3].optionContent" @focus="updateTinymceContent(subjectInfo.options[3].optionContent, tinymceEdit.optionD)"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.answer')" prop="answer">
<!-- 选择题 -->
<el-radio-group v-model="subjectInfo.answer.answer">
<el-radio :label="'A'">A</el-radio>
<el-radio :label="'B'">B</el-radio>
<el-radio :label="'C'">C</el-radio>
<el-radio :label="'D'">D</el-radio>
<el-radio v-for="(option) in options" :label="option.optionName" :key="option.optionName">{{ option.optionName }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.analysis')" prop="analysis">
<el-input v-model="subjectInfo.analysis" @focus="updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)"/>
</el-form-item>
</el-col>
</el-row>
<el-collapse v-model="optionCollapseActives">
<el-collapse-item title="选项列表" name="1">
<el-row class="collapse-top">
<el-col :span="24">
<el-form-item v-for="(option, index) in options" :label="option.optionName" :key="option.optionName"
:prop="'options.' + index + '.optionContent'">
<el-row :gutter="5">
<el-col :span="2">
<el-input v-model="option.optionName"></el-input>
</el-col>
<el-col :span="20">
<el-input v-model="option.optionContent" @input="updateTinymceContent(option.optionContent, index, '1')">
<el-button slot="append" @click.prevent="removeOption(option)">删除</el-button>
</el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-button @click.prevent="addOption()" style="display:block;margin:0 auto">新增选项</el-button>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
<el-collapse v-model="analysisCollapseActives">
<el-collapse-item title="解析" name="2">
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.analysis')" prop="analysis">
<el-input v-model="subjectInfo.analysis" @input="updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)"/>
</el-form-item>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
</div>
</el-col>
<el-col :span="14">
<div class="subject-tinymce">
<tinymce ref="choicesEditor" :height="350" v-model="choicesContent"/>
<tinymce ref="choicesEditor" :height="350" v-model="choicesContent" @hasClick="hasClick"/>
</div>
</el-col>
</el-row>
......@@ -84,7 +82,7 @@
<script>
import Tinymce from '@/components/Tinymce'
import { isNotEmpty } from '@/utils/util'
import { isNotEmpty, message } from '@/utils/util'
export default {
name: 'Choices',
......@@ -97,7 +95,6 @@ export default {
default: function () {
return {
id: '',
serialNumber: 1,
examinationId: '',
categoryId: 0,
subjectName: '',
......@@ -117,7 +114,8 @@ export default {
},
score: 5,
analysis: '',
level: 2
level: 2,
editType: 0 // 0: 输入框,1:富文本
}
}
},
......@@ -138,37 +136,54 @@ export default {
answer: [{ required: true, message: '请输入答案', trigger: 'change' }]
},
tinymce: {
type: 1, // 类型 0:题目名称,1:选项A,2:选择B,3:选项C,4:选项D
type: 1, // 类型 0:题目名称,1:选项
dialogTinymceVisible: false,
tempValue: '',
currentEdit: -1
currentEdit: -1,
},
// 编辑对象
tinymceEdit: {
subjectName: -1,
optionA: 0,
optionB: 1,
optionC: 2,
optionD: 3,
answer: 4,
analysis: 5
}
},
options: [],
optionCollapseActives: ['1'],
analysisCollapseActives: ['2']
}
},
watch: {
// 监听富文本编辑器的输入
choicesContent: {
handler: function (choicesContent) {
this.saveTinymceContent(choicesContent)
if (isNotEmpty(this.$refs.choicesEditor)) {
if (this.editType === 1 && this.$refs.choicesEditor.getHasClick()) {
this.saveTinymceContent(choicesContent)
}
}
},
immediate: true
}
},
methods: {
initDefaultOptions () {
this.options = [
{subjectChoicesId: '', optionName: 'A', optionContent: ''},
{subjectChoicesId: '', optionName: 'B', optionContent: ''},
{subjectChoicesId: '', optionName: 'C', optionContent: ''},
{subjectChoicesId: '', optionName: 'D', optionContent: ''}
]
},
setSubjectInfo (subject) {
this.subjectInfo = subject
if (this.subjectInfo.options.length > 0) {
this.options = this.subjectInfo.options
} else {
this.initDefaultOptions()
}
},
getSubjectInfo () {
this.subjectInfo.options = this.options
return this.subjectInfo
},
setChoicesContent (choicesContent) {
......@@ -178,38 +193,33 @@ export default {
return this.choicesContent
},
// 绑定富文本的内容
updateTinymceContent (content, currentEdit) {
updateTinymceContent (content, currentEdit, type) {
// 重置富文本
this.choicesContent = ''
// 绑定当前编辑的对象
this.tinymce.currentEdit = currentEdit
this.tinymce.type = type
// 选择题
this.$refs.choicesEditor.setContent(content || '')
this.editType = 0
this.$refs.choicesEditor.setHashClick(false)
},
// 保存题目时绑定富文本的内容到subjectInfo
saveTinymceContent (content) {
switch (this.tinymce.currentEdit) {
case this.tinymceEdit.subjectName:
this.subjectInfo.subjectName = content
break
case this.tinymceEdit.optionA:
this.subjectInfo.options[0].optionContent = content
break
case this.tinymceEdit.optionB:
this.subjectInfo.options[1].optionContent = content
break
case this.tinymceEdit.optionC:
this.subjectInfo.options[2].optionContent = content
break
case this.tinymceEdit.optionD:
this.subjectInfo.options[3].optionContent = content
break
case this.tinymceEdit.answer:
this.subjectInfo.answer.answer = content
break
case this.tinymceEdit.analysis:
this.subjectInfo.analysis = content
break
if (this.tinymce.type !== '1') {
switch (this.tinymce.currentEdit) {
case this.tinymceEdit.subjectName:
this.subjectInfo.subjectName = content
break
case this.tinymceEdit.answer:
this.subjectInfo.answer.answer = content
break
case this.tinymceEdit.analysis:
this.subjectInfo.analysis = content
break
}
} else {
this.options[this.tinymce.currentEdit].optionContent = content
}
},
// 表单校验
......@@ -223,10 +233,9 @@ export default {
clearValidate () {
this.$refs['dataSubjectForm'].clearValidate()
},
resetTempSubject (serialNumber, score) {
resetTempSubject (score) {
this.subjectInfo = {
id: '',
serialNumber: 1,
examinationId: '',
categoryId: 0,
subjectName: '',
......@@ -248,20 +257,44 @@ export default {
analysis: '',
level: 2
}
// 默认序号
if (isNotEmpty(serialNumber)) {
this.subjectInfo.serialNumber = serialNumber
}
// 默认分数
if (isNotEmpty(score)) {
this.subjectInfo.score = score
}
this.initDefaultOptions()
},
addOption() {
// 校验
if (this.options.length > 0) {
let option = this.options[this.options.length - 1]
if (!isNotEmpty(option.optionName)) {
message(this, '请先输入选项再添加', 'warning')
return
}
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' });
} else {
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' });
}
},
removeOption(item) {
let index = this.options.indexOf(item)
if (index !== -1) {
this.options.splice(index, 1)
}
},
// 点击事件回调
hasClick(hasClick) {
this.editType = 1
}
}
}
</script>
<style lang="scss" scoped>
@import "../../../styles/subject.scss";
.el-rate {
margin-top: 8px;
}
</style>
......@@ -25,57 +25,55 @@
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.optionA')">
<el-input v-model="subjectInfo.options[0].optionContent" @focus="updateTinymceContent(subjectInfo.options[0].optionContent, tinymceEdit.optionA)"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.optionB')">
<el-input v-model="subjectInfo.options[1].optionContent" @focus="updateTinymceContent(subjectInfo.options[1].optionContent, tinymceEdit.optionB)"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.optionC')">
<el-input v-model="subjectInfo.options[2].optionContent" @focus="updateTinymceContent(subjectInfo.options[2].optionContent, tinymceEdit.optionC)"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.optionD')">
<el-input v-model="subjectInfo.options[3].optionContent" @focus="updateTinymceContent(subjectInfo.options[3].optionContent, tinymceEdit.optionD)"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.answer')" prop="answer">
<!-- 选择题 -->
<el-radio-group v-model="subjectInfo.answer.answer">
<el-radio :label="'A'">A</el-radio>
<el-radio :label="'B'">B</el-radio>
<el-radio :label="'C'">C</el-radio>
<el-radio :label="'D'">D</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.analysis')" prop="analysis">
<el-input v-model="subjectInfo.analysis" @focus="updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)"/>
<el-checkbox-group v-model="multipleAnswers">
<el-checkbox v-for="(option) in options" :label="option.optionName" :key="option.optionName" :name="option.optionName">{{ option.optionName }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
</el-row>
<el-collapse v-model="optionCollapseActives">
<el-collapse-item title="选项列表" name="1">
<el-row class="collapse-top">
<el-col :span="24">
<el-form-item v-for="(option, index) in options" :label="option.optionName" :key="option.optionName"
:prop="'options.' + index + '.optionContent'">
<el-row :gutter="5">
<el-col :span="2">
<el-input v-model="option.optionName"></el-input>
</el-col>
<el-col :span="20">
<el-input v-model="option.optionContent" @input="updateTinymceContent(option.optionContent, index, '1')">
<el-button slot="append" @click.prevent="removeOption(option)">删除</el-button>
</el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-button @click.prevent="addOption()" style="display:block;margin:0 auto">新增选项</el-button>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
<el-collapse v-model="analysisCollapseActives">
<el-collapse-item title="解析" name="2">
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.analysis')" prop="analysis">
<el-input v-model="subjectInfo.analysis" @input="updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)"/>
</el-form-item>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
</div>
</el-col>
<el-col :span="14">
<div class="subject-tinymce">
<tinymce ref="choicesEditor" :height="350" v-model="choicesContent"/>
<tinymce ref="choicesEditor" :height="350" v-model="choicesContent" @hasClick="hasClick"/>
</div>
</el-col>
</el-row>
......@@ -97,18 +95,10 @@ export default {
default: function () {
return {
id: '',
serialNumber: 1,
examinationId: '',
categoryId: 0,
subjectName: '',
type: 3,
choicesType: 1,
options: [
{subjectChoicesId: '', optionName: 'A', optionContent: ''},
{subjectChoicesId: '', optionName: 'B', optionContent: ''},
{subjectChoicesId: '', optionName: 'C', optionContent: ''},
{subjectChoicesId: '', optionName: 'D', optionContent: ''}
],
choicesContent: this.choices,
answer: {
subjectId: '',
answer: '',
......@@ -138,37 +128,57 @@ export default {
answer: [{ required: true, message: '请输入答案', trigger: 'change' }]
},
tinymce: {
type: 1, // 类型 0:题目名称,1:选项A,2:选择B,3:选项C,4:选项D
type: 1, // 类型 0:题目名称,1:选项
dialogTinymceVisible: false,
tempValue: '',
currentEdit: -1
currentEdit: -1,
},
// 编辑对象
tinymceEdit: {
subjectName: -1,
optionA: 0,
optionB: 1,
optionC: 2,
optionD: 3,
answer: 4,
analysis: 5
}
},
options: [],
optionCollapseActives: ['1'],
analysisCollapseActives: ['2'],
multipleAnswers: []
}
},
watch: {
// 监听富文本编辑器的输入
choicesContent: {
handler: function (choicesContent) {
this.saveTinymceContent(choicesContent)
if (isNotEmpty(this.$refs.choicesEditor)) {
if (this.editType === 1 && this.$refs.choicesEditor.getHasClick()) {
this.saveTinymceContent(choicesContent)
}
}
},
immediate: true
}
},
methods: {
initDefaultOptions () {
this.options = [
{subjectChoicesId: '', optionName: 'A', optionContent: ''},
{subjectChoicesId: '', optionName: 'B', optionContent: ''},
{subjectChoicesId: '', optionName: 'C', optionContent: ''},
{subjectChoicesId: '', optionName: 'D', optionContent: ''}
]
},
setSubjectInfo (subject) {
this.subjectInfo = subject
if (isNotEmpty(this.subjectInfo.options) && this.subjectInfo.options.length > 0) {
this.options = this.subjectInfo.options
} else {
this.initDefaultOptions()
}
this.initMultipleAnswers()
},
getSubjectInfo () {
this.subjectInfo.options = this.options
this.subjectInfo.answer.answer = this.getMultipleAnswers()
return this.subjectInfo
},
setChoicesContent (choicesContent) {
......@@ -178,38 +188,33 @@ export default {
return this.choicesContent
},
// 绑定富文本的内容
updateTinymceContent (content, currentEdit) {
updateTinymceContent (content, currentEdit, type) {
// 重置富文本
this.choicesContent = ''
// 绑定当前编辑的对象
this.tinymce.currentEdit = currentEdit
this.tinymce.type = type
// 选择题
this.$refs.choicesEditor.setContent(content || '')
this.editType = 0
this.$refs.choicesEditor.setHashClick(false)
},
// 保存题目时绑定富文本的内容到subjectInfo
saveTinymceContent (content) {
switch (this.tinymce.currentEdit) {
case this.tinymceEdit.subjectName:
this.subjectInfo.subjectName = content
break
case this.tinymceEdit.optionA:
this.subjectInfo.options[0].optionContent = content
break
case this.tinymceEdit.optionB:
this.subjectInfo.options[1].optionContent = content
break
case this.tinymceEdit.optionC:
this.subjectInfo.options[2].optionContent = content
break
case this.tinymceEdit.optionD:
this.subjectInfo.options[3].optionContent = content
break
case this.tinymceEdit.answer:
this.subjectInfo.answer.answer = content
break
case this.tinymceEdit.analysis:
this.subjectInfo.analysis = content
break
if (this.tinymce.type !== '1') {
switch (this.tinymce.currentEdit) {
case this.tinymceEdit.subjectName:
this.subjectInfo.subjectName = content
break
case this.tinymceEdit.answer:
this.subjectInfo.answer.answer = content
break
case this.tinymceEdit.analysis:
this.subjectInfo.analysis = content
break
}
} else {
this.options[this.tinymce.currentEdit].optionContent = content
}
},
// 表单校验
......@@ -226,11 +231,10 @@ export default {
resetTempSubject (serialNumber, score) {
this.subjectInfo = {
id: '',
serialNumber: 1,
examinationId: '',
categoryId: 0,
subjectName: '',
type: 3,
type: 0,
choicesType: 0,
options: [
{ subjectChoicesId: '', optionName: 'A', optionContent: '' },
......@@ -248,15 +252,45 @@ export default {
analysis: '',
level: 2
}
// 默认序号
if (isNotEmpty(serialNumber)) {
this.subjectInfo.serialNumber = serialNumber
}
// 默认分数
if (isNotEmpty(score)) {
this.subjectInfo.score = score
}
this.initDefaultOptions()
},
addOption() {
// 校验
if (this.options.length > 0) {
let option = this.options[this.options.length - 1]
if (!isNotEmpty(option.optionName)) {
message(this, '请先输入选项再添加', 'warning')
return
}
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' });
} else {
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' });
}
},
removeOption(item) {
let index = this.options.indexOf(item)
if (index !== -1) {
this.options.splice(index, 1)
}
},
// 点击事件回调
hasClick(hasClick) {
this.editType = 1
},
initMultipleAnswers() {
if (isNotEmpty(this.subjectInfo.answer)) {
this.multipleAnswers = this.subjectInfo.answer.answer.split(',')
}
},
getMultipleAnswers() {
if (this.multipleAnswers.length > 0) {
return this.multipleAnswers.join(',')
}
return ''
}
}
}
......
......@@ -175,15 +175,13 @@ export default {
analysis: '',
level: 2
}
// 默认序号
if (isNotEmpty(serialNumber)) {
this.subjectInfo.serialNumber = serialNumber
}
// 默认分数
if (isNotEmpty(score)) {
this.subjectInfo.score = score
}
},
initDefaultOptions() {
}
}
}
......
......@@ -43,6 +43,7 @@ export default {
data () {
return {
hasChange: false,
hasClick: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
......@@ -112,6 +113,11 @@ export default {
this.hasChange = true
this.$emit('input', editor.getContent())
})
editor.on('Click', () => {
this.hasClick = true
this.$emit('hasClick', this.hasClick)
})
},
setup (editor) {
editor.on('FullscreenStateChanged', (e) => {
......@@ -130,6 +136,12 @@ export default {
},
getContent () {
return window.tinymce.get(this.tinymceId).getContent()
},
getHasClick () {
return this.hasClick
},
setHashClick (click) {
this.hasClick = click
}
}
}
......
import { formatDate, commonFilter, isNotEmpty } from '../utils/util'
import { statusType, examType, subjectType } from '../utils/constant'
/**
* 日期格式化
* @param date
* @param format
* @returns {*}
*/
export function fmtDate (date, format) {
if (!isNotEmpty(date)) {
return ''
}
return formatDate(new Date(date), format)
}
/**
* 通用状态
* @param status
* @returns {*}
*/
export function statusTypeFilter (status) {
return statusType[status]
}
/**
* 截取指定长度
* @param str
* @param length
* @returns {string}
*/
export function simpleStrFilter (str, length) {
return commonFilter(str, length)
}
/**
* 考试类型
* @param type
* @returns {*}
*/
export function examTypeFilter (type) {
return examType[type]
}
/**
* 发布类型
* @param status
* @returns {string}
*/
export function examStatusFilter (status) {
return status === 0 ? '已发布' : '未发布'
}
/**
* 题目类型
* @param type
* @returns {*}
*/
export function subjectTypeFilter (type) {
return subjectType[type]
}
......@@ -138,6 +138,7 @@ export default {
isDefault: '是否默认',
actions: '操作',
edit: '修改',
view: '查看',
publish: '发布',
draft: '草稿',
delete: '删除',
......@@ -196,7 +197,9 @@ export default {
optionE: '选项E',
optionF: '选项F',
answer: '参考答案',
analysis: '解析'
analysis: '解析',
modifyDate: '修改时间',
modifier: '修改人'
},
public: '发布',
retrieve: '回收',
......
......@@ -108,8 +108,37 @@ export const constantRouterMap = [
}
]
export const constantExamRouterMap = [
{
path: '',
component: Layout,
children: [
{
path: '/exam/exam/subjects/:id',
component: () => import('@/views/exam/examSubjects'),
name: '题目管理',
title: '题目管理',
noCache: true
}
]
},
{
path: '',
component: Layout,
children: [
{
path: '/exam/:examinationId/subjects/:id/:type',
component: () => import('@/views/exam/subjectDetails'),
name: '题目详情',
title: '题目详情',
noCache: true
}
]
}
]
export default new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: [].concat(...formatRoutes(store.state.user.menu), constantRouterMap)
routes: [].concat(...formatRoutes(store.state.user.menu), constantRouterMap, constantExamRouterMap)
})
......@@ -20,3 +20,6 @@
.subject-tinymce {
padding-left: 12px;
}
.collapse-top {
margin-top: 20px;
}
export const dialogStatusConstant = {
create: 'create',
update: 'update'
}
export const statusType = {
0: 'success',
1: 'warning'
}
// 考试类型
export const examType = {
0: '正式考试',
1: '模拟考试',
2: '在线练习'
}
// 题目类型
export const subjectType = {
0: '单选题',
1: '简答题',
3: '多选题'
}
......@@ -369,3 +369,23 @@ export const isSuccess = (response) => {
}
return success
}
/**
* 截取指定长度
* @param str
* @param length
* @returns {string}
*/
export const commonFilter = (str, length) => {
if (str.length > length) {
return str.substring(0, length) + '...'
}
return str
}
/**
* 新建状态
*/
export const isCreate = (status) => {
return status === 'create'
}
......@@ -20,12 +20,12 @@
<el-table-column type="selection" width="55"/>
<el-table-column :label="$t('table.examinationName')">
<template slot-scope="scope">
<span>{{ scope.row.examinationName }}</span>
<span>{{ scope.row.examinationName | simpleStrFilter }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.type')">
<template slot-scope="scope">
<span>{{ scope.row.type | typeFilter }}</span>
<span>{{ scope.row.type | examTypeFilter }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.course')">
......@@ -35,12 +35,12 @@
</el-table-column>
<el-table-column :label="$t('table.startTime')">
<template slot-scope="scope">
<span>{{ scope.row.startTime | timeFilter }}</span>
<span>{{ scope.row.startTime | fmtDate('yyyy-MM-dd hh:mm') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.endTime')">
<template slot-scope="scope">
<span>{{ scope.row.endTime | timeFilter }}</span>
<span>{{ scope.row.endTime | fmtDate('yyyy-MM-dd hh:mm') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.totalScore')">
......@@ -50,7 +50,7 @@
</el-table-column>
<el-table-column :label="$t('table.status')">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusTypeFilter">{{ scope.row.status | statusFilter }}</el-tag>
<el-tag :type="scope.row.status | statusTypeFilter ">{{ scope.row.status | examStatusFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300">
......@@ -186,192 +186,16 @@
</el-table-column>
</el-table>
</el-dialog>
<!--题目管理列表-->
<el-dialog :visible.sync="dialogSubjectVisible" :title="$t('table.subjectManagement')" width="80%" top="5vh">
<div class="filter-container">
<el-input :placeholder="$t('table.subjectName')" v-model="subject.listQuery.subjectName" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilterSubject"/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilterSubject">{{ $t('table.search') }}</el-button>
<el-button v-if="exam_btn_subject_add" class="filter-item" icon="el-icon-check" plain @click="handleCreateSubject">{{ $t('table.add') }}</el-button>
<el-button v-if="exam_btn_subject_add" class="filter-item" icon="el-icon-check" plain @click="handleCreateSubjectFromSubjectBank">{{ $t('table.addFromSubjectBank') }}</el-button>
<el-button v-if="exam_btn_subject_import" class="filter-item" icon="el-icon-upload2" plain @click="handleImportSubject">{{ $t('table.import') }}</el-button>
<el-button v-if="exam_btn_subject_export" class="filter-item" icon="el-icon-download" plain @click="handleExportSubject">{{ $t('table.export') }}</el-button>
</div>
<el-table
:data="subject.list"
v-loading="subject.listLoading"
highlight-current-row
style="width: 100%;"
@selection-change="handleSubjectSelectionChange"
@cell-dblclick="handleUpdateSubject"
@sort-change="sortSubjectChange">
<el-table-column type="selection" width="55"/>
<el-table-column :label="$t('table.subjectName')" min-width="120">
<template slot-scope="scope">
<span>{{ scope.row.subjectName | subjectNameFilter }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.type')" width="120">
<template slot-scope="scope">
<el-tag type="success">{{ scope.row.type | subjectTypeFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.score')" property="score" width="120">
<template slot-scope="scope">
<span>{{ scope.row.score }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.level')" property="level" width="120">
<template slot-scope="scope">
<el-rate v-model="scope.row.level" :max="4"/>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300px">
<template slot-scope="scope">
<el-button v-if="exam_btn_subject" type="text" @click="handleUpdateSubject(scope.row)" icon="el-icon-edit">{{ $t('table.edit') }}</el-button>
<el-button v-if="exam_btn_del" type="text" @click="handleDeleteSubject(scope.row)" icon="el-icon-delete">{{ $t('table.delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination v-show="subject.total>0" :current-page="subject.listQuery.pageNum" :page-sizes="[10,20,30, 50]" :page-size="subject.listQuery.pageSize" :total="subject.total" background layout="total, sizes, prev, pager, next, jumper" @size-change="handleSubjectSizeChange" @current-change="handleSubjectCurrentChange"/>
</div>
</el-dialog>
<!--题目信息表单-->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogSubjectFormVisible" width="80%" top="5vh">
<el-tabs v-model="activeName" @tab-click="handleTabChange">
<!-- 单选题 -->
<el-tab-pane label="单选题" name="0" :disabled="tempSubject.type !== 0 && dialogStatus !== 'create'">
<choices ref="choices" subjectInfo="tempSubject"></choices>
</el-tab-pane>
<!-- 多选题 -->
<el-tab-pane label="多选题" name="3" :disabled="tempSubject.type !== 3 && dialogStatus !== 'create'">
<multiple-choices ref="multipleChoices" subjectInfo="tempSubject"></multiple-choices>
</el-tab-pane>
<!-- 简答题 -->
<el-tab-pane label="简答题" name="1" :disabled="tempSubject.type !== 1 && dialogStatus !== 'create'">
<short-answer ref="shortAnswer" subjectInfo="tempSubject"></short-answer>
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogSubjectFormVisible = false">{{ $t('table.cancel') }}</el-button>
<el-button v-if="dialogStatus === 'create'" type="primary" @click="createSubjectData">{{ $t('table.save') }}</el-button>
<el-button v-else type="primary" @click="updateSubjectData">{{ $t('table.save') }}</el-button>
<el-button type="primary" @click="updateAndAddSubjectData">{{ $t('table.saveAndAdd') }}</el-button>
</div>
</el-dialog>
<!-- 导入题目 -->
<el-dialog :visible.sync="dialogImportVisible" :title="$t('table.import')">
<el-row>
<el-col :span="24">
<el-upload
drag
:multiple="false"
:auto-upload="true"
:show-file-list="true"
:before-upload="beforeUploadSubjectUpload"
:on-progress="handleUploadSubjectProgress"
:on-success="handleUploadSubjectSuccess"
:action="importUrl"
:headers="headers"
:data="params"
style="text-align: center;">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div slot="tip" class="el-upload__tip">只能上传xlsx文件</div>
</el-upload>
</el-col>
</el-row>
</el-dialog>
<!-- 题库列表 -->
<el-dialog title="选择题目" :visible.sync="category.dialogVisible" width="80%" top="10vh">
<el-row>
<el-col :span="4">
<el-card class="tree-box-card" style="margin-right: 5px;">
<div slot="header">
<span>题目分类</span>
</div>
<el-row>
<div class="tree-container">
<el-tree
:data="category.treeData"
:props="category.defaultProps"
class="filter-tree"
node-key="id"
highlight-current
accordion
@node-click="getNodeData"
/>
</div>
</el-row>
</el-card>
</el-col>
<el-col :span="20">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>选择题目</span>
</div>
<el-table
v-loading="category.listLoading"
:data="category.list"
:default-sort="{ prop: 'id', order: 'ascending' }"
highlight-current-row
style="width: 100%;"
@row-click = "handleSingleSubjectSelection"
@current-change="handleSingleSubjectCurrentChange">
<el-table-column align="center" width="55" label="" >
<template slot-scope="scope">
<el-radio :label="scope.$index" v-model="category.tempRadio" @change.native="handleSingleSubjectSelectionChange(scope.$index, scope.row)">&nbsp;</el-radio>
</template>
</el-table-column>
<el-table-column :label="$t('table.subjectName')" sortable prop="subject_name" property="subjectName" min-width="120">
<template slot-scope="scope">
<span>{{ scope.row.subjectName }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.type')">
<template slot-scope="scope">
<el-tag type="success">{{ scope.row.type | subjectTypeFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.score')">
<template slot-scope="scope">
<span>{{ scope.row.score }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.level')">
<template slot-scope="scope">
<el-rate v-model="scope.row.level" :max="4"/>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination v-show="category.total>0" :current-page="category.listQuery.pageNum" :page-sizes="[10,20,30, 50]" :page-size="category.listQuery.pageSize" :total="category.total" background layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange"/>
</div>
</el-card>
</el-col>
</el-row>
<div slot="footer" class="dialog-footer">
<el-button @click="category.dialogVisible = false">{{ $t('table.cancel') }}</el-button>
<el-button type="primary" @click="handleSelectSubject">{{ $t('table.confirm') }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { fetchList, fetchSubjectListById, addObj, putObj, delObj, delAllObj } from '@/api/exam/exam'
import { fetchList, addObj, putObj, delObj, delAllObj } from '@/api/exam/exam'
import { fetchCourseList } from '@/api/exam/course'
import { fetchSubjectList, getSubject, addSubject, putSubject, delSubject, delAllSubject, exportSubject } from '@/api/exam/subject'
import { fetchCategoryTree } from '@/api/exam/subjectCategory'
import waves from '@/directive/waves'
import { mapGetters, mapState } from 'vuex'
import { getToken } from '@/utils/auth'
import { checkMultipleSelect, exportExcel, isNotEmpty, notifySuccess, notifyFail, messageSuccess, formatDate } from '@/utils/util'
import { checkMultipleSelect, isNotEmpty, notifySuccess, notifyFail, messageSuccess } from '@/utils/util'
import { delAttachment, preview } from '@/api/admin/attachment'
import Tinymce from '@/components/Tinymce'
import SpinnerLoading from '@/components/SpinnerLoading'
......@@ -386,46 +210,11 @@ export default {
},
components: { Tinymce, SpinnerLoading, Choices, MultipleChoices, ShortAnswer },
filters: {
statusTypeFilter (status) {
const statusMap = {
0: 'success',
1: 'warning'
}
return statusMap[status]
},
statusFilter (status) {
return status === 0 ? '已发布' : '未发布'
},
typeFilter (type) {
const typeMap = {
0: '正式考试',
1: '模拟考试',
2: '在线练习'
}
return typeMap[type]
},
subjectTypeFilter (type) {
const typeMap = {
0: '单选题',
1: '简答题',
3: '多选题'
}
return typeMap[type]
},
subjectNameFilter (subjectName) {
if (subjectName.length > 50) {
return subjectName.substring(0, 50) + '...'
}
return subjectName
},
courseFilter (row) {
if (isNotEmpty(row.course) && isNotEmpty(row.course.courseName)) {
return row.course.courseName
}
return ''
},
timeFilter (time) {
return formatDate(new Date(time), 'yyyy-MM-dd hh:mm')
}
},
data () {
......@@ -459,22 +248,6 @@ export default {
total: null,
listLoading: true
},
// 题目
subject: {
listQuery: {
pageNum: 1,
pageSize: 10,
examinationId: '',
categoryId: '',
sort: 'id',
order: 'ascending'
},
list: null,
total: null,
listLoading: true,
examinationId: '',
categoryId: ''
},
// 考试临时信息
temp: {
id: '',
......@@ -497,42 +270,6 @@ export default {
remark: ''
},
avatar: null,
// 题目临时信息
tempSubject: {
id: '',
examinationId: '',
categoryId: 0,
subjectName: '',
type: 0,
choicesType: 0,
options: [
{ subjectChoicesId: '', optionName: 'A', optionContent: '' },
{ subjectChoicesId: '', optionName: 'B', optionContent: '' },
{ subjectChoicesId: '', optionName: 'C', optionContent: '' },
{ subjectChoicesId: '', optionName: 'D', optionContent: '' }
],
answer: {
subjectId: '',
answer: '',
answerType: '',
score: ''
},
score: 5,
analysis: '',
level: 2
},
tempSubjectTypeList: [
{ name: '单选题', type: 0 },
{ name: '简答题', type: 1 },
{ name: '多选题', type: 3 }
],
// 选择题类型
tempChoiceType: [
{ type: 0, name: '单选题' },
{ type: 1, name: '简答题' },
{ type: 2, name: '判断题' },
{ type: 3, name: '多选题' }
],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
......@@ -554,68 +291,14 @@ export default {
exam_btn_edit: false,
exam_btn_del: false,
exam_btn_subject: false,
exam_btn_subject_import: false,
exam_btn_subject_export: false,
dialogCourseVisible: false,
courseData: [],
dialogSubjectVisible: false,
subjectData: [],
dialogSubjectFormVisible: false,
// 题目类型
subjectTypeData: [
{ id: 0, subjectTypeName: '单选题' },
{ id: 1, subjectTypeName: '简答题' },
{ id: 3, subjectTypeName: '多选题' }
],
// 多选考试
multipleSelection: [],
// 多选题目
multipleSubjectSelection: [],
// 单选题目
singleSubjectSelection: [],
// 导入弹窗状态
dialogImportVisible: false,
// 导入题目的url
importUrl: '/api/exam/v1/subject/import',
uploading: false,
percentage: 0,
uploadingSubject: false,
percentageSubject: 0,
// 题目分类数据
category: {
dialogVisible: false,
// 题目列表查询参数
listQuery: {
subjectName: undefined,
categoryId: '',
sort: 'id',
order: 'ascending'
},
// 题目列表数据
list: [],
// 分类树数据
treeData: [],
// 题目分类数据
defaultProps: {
children: 'children',
label: 'categoryName'
},
// 列表加载状态
listLoading: false,
tempRadio: ''
},
activeName: '0',
choicesContent: '',
// 编辑对象
tinymceEdit: {
subjectName: -1,
optionA: 0,
optionB: 1,
optionC: 2,
optionD: 3,
answer: 4,
analysis: 5
}
activeName: '0'
}
},
created () {
......@@ -631,10 +314,6 @@ export default {
this.exam_btn_edit = this.permissions['exam:exam:edit']
this.exam_btn_del = this.permissions['exam:exam:del']
this.exam_btn_subject = this.permissions['exam:exam:subject']
this.exam_btn_subject_add = this.permissions['exam:exam:subject:add']
this.exam_btn_subject_del = this.permissions['exam:exam:subject:del']
this.exam_btn_subject_import = this.permissions['exam:exam:subject:import']
this.exam_btn_subject_export = this.permissions['exam:exam:subject:export']
},
computed: {
...mapGetters([
......@@ -672,14 +351,6 @@ export default {
this.listQuery.pageNum = val
this.getList()
},
handleSubjectSizeChange (val) {
this.subject.listQuery.limit = val
this.handleSubjectManagement()
},
handleSubjectCurrentChange (val) {
this.subject.listQuery.pageNum = val
this.handleSubjectManagement()
},
handleModifyStatus (row, status) {
row.status = status
putObj(row).then(() => {
......@@ -690,68 +361,12 @@ export default {
handleSelectionChange (val) {
this.multipleSelection = val
},
handleSubjectSelectionChange (val) {
this.multipleSubjectSelection = val
},
// 题库里选择题目
handleSingleSubjectSelectionChange (index, row) {
this.category.singleSubjectSelection = row
},
// 点击行时选择题目
handleSingleSubjectSelection (row) {
this.category.tempRadio = this.category.list.indexOf(row)
},
// 表格变化
handleSingleSubjectCurrentChange (row) {
this.category.singleSubjectSelection = row
},
// 选择题目
handleSelectSubject () {
// 加载题目信息
getSubject(this.category.singleSubjectSelection.id, { type: this.category.singleSubjectSelection.type }).then(response => {
this.tempSubject = response.data.data
// 隐藏弹框
this.category.dialogVisible = false
// 清空题目ID
this.tempSubject.id = ''
// 清空分类ID
this.tempSubject.categoryId = ''
// 清空选项ID
this.tempSubject.options.forEach(option => {
option.id = ''
})
// 绑定考试ID
this.tempSubject.examinationId = this.subject.examinationId
// 状态为新建
this.dialogStatus = 'create'
// 显示题目信息表单
this.dialogSubjectFormVisible = true
// 切换到对应的题型选项卡
this.updateCurrentTag(this.tempSubject.type)
// 更新组件里的题目信息
setTimeout(() => {
this.updateComponentSubjectInfo()
}, 200)
})
},
// 排序事件
sortChange (column, prop, order) {
this.listQuery.sort = column.prop
this.listQuery.order = column.order
this.getList()
},
sortSubjectChange (column, prop, order) {
this.subject.listQuery.sort = column.prop
this.subject.listQuery.order = column.order
this.handleSubjectManagement()
},
// 点击分类
getNodeData (data) {
// 获取分类ID
this.category.listQuery.categoryId = data.id
// 获取题目信息
this.handleSubjectBankManagement()
},
resetTemp () {
this.temp = {
id: '',
......@@ -795,7 +410,7 @@ export default {
})
},
handleUpdate (row) {
this.temp = Object.assign({}, row) // copy obj
this.temp = Object.assign({}, row)
if (!isNotEmpty(this.temp.course)) {
this.temp.course = {
id: '',
......@@ -886,164 +501,9 @@ export default {
},
// 加载题目
handleSubjectManagement (row) {
this.subject.listLoading = true
// 保存当前题目列表的考试ID
if (row !== undefined) {
this.subject.examinationId = row.id
this.subject.listQuery.examinationId = row.id
this.params.examinationId = row.id
}
fetchSubjectListById(this.subject.listQuery).then(response => {
if (response.data.list.length > 0) {
for (let i = 0; i < response.data.list.length; i++) {
const subject = response.data.list[i]
subject.type = parseInt(subject.type)
subject.level = parseInt(subject.level)
}
}
this.subject.list = response.data.list
this.subject.total = parseInt(response.data.total)
setTimeout(() => {
this.subject.listLoading = false
}, 500)
this.$router.push({
path: `/exam/exam/subjects/${row.id}`,
})
this.dialogSubjectVisible = true
},
// 加载题库列表
handleSubjectBankManagement () {
this.category.listLoading = true
fetchSubjectList(this.category.listQuery).then(response => {
if (response.data.list.length > 0) {
for (let i = 0; i < response.data.list.length; i++) {
const subject = response.data.list[i]
subject.type = parseInt(subject.type)
subject.level = parseInt(subject.level)
}
}
this.category.list = response.data.list
this.category.total = parseInt(response.data.total)
this.category.listLoading = false
})
},
handleFilterSubject () {
this.subject.listQuery.pageNum = 1
this.handleSubjectManagement()
},
// 新建题目
handleCreateSubject () {
this.resetTempSubject()
this.dialogStatus = 'create'
this.dialogSubjectFormVisible = true
this.resetActiveName()
},
// 从题库新增
handleCreateSubjectFromSubjectBank () {
// 加载分类树
fetchCategoryTree(this.category.listQuery).then(response => {
this.category.treeData = response.data
})
this.category.dialogVisible = true
// 加载题目列表
},
resetTempSubject (serialNumber, score) {
const ref = this.getSubjectRef()
if (isNotEmpty(ref)) {
ref.resetTempSubject(serialNumber, score)
}
},
// 修改题目
handleUpdateSubject (row) {
// 加载选项信息
getSubject(row.id, { type: row.type }).then(response => {
const subjectInfo = response.data.data
this.dialogStatus = 'update'
this.dialogSubjectFormVisible = true
// 切换到对应的题型选项卡
this.updateCurrentTag(subjectInfo.type)
setTimeout(() => {
const ref = this.getSubjectRef()
if (isNotEmpty(ref)) {
// 初始化单选题
this.$nextTick(() => {
ref.clearValidate()
ref.setSubjectInfo(subjectInfo)
})
}
}, 200)
})
},
// 删除题目
handleDeleteSubject (row) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delSubject(row.id, { type: row.type }).then(() => {
this.dialogSubjectFormVisible = false
this.handleSubjectManagement()
notifySuccess(this, '删除成功')
})
}).catch(() => {})
},
// 保存题目
createSubjectData () {
const ref = this.getSubjectRef()
if (ref.validate()) {
let subjectInfo = ref.getSubjectInfo()
// 绑定考试ID
subjectInfo.examinationId = this.subject.examinationId
addSubject(subjectInfo).then(() => {
this.subject.list.unshift(subjectInfo)
this.dialogSubjectFormVisible = false
this.handleSubjectManagement()
notifySuccess(this, '创建成功')
})
}
},
// 更新题目
updateSubjectData () {
const ref = this.getSubjectRef()
if (ref.validate()) {
const subjectInfo = ref.getSubjectInfo()
putSubject(subjectInfo).then(() => {
this.dialogSubjectFormVisible = false
this.handleSubjectManagement()
notifySuccess(this, '更新成功')
})
}
},
// 更新并添加题目
updateAndAddSubjectData () {
const ref = this.getSubjectRef()
if (ref.validate()) {
const subjectInfo = ref.getSubjectInfo()
// 绑定考试ID
subjectInfo.examinationId = this.subject.examinationId
// 创建
if (this.dialogStatus === 'create') {
addSubject(subjectInfo).then(() => {
this.resetTempSubject(parseInt(subjectInfo.serialNumber) + 1, subjectInfo.score)
this.dialogStatus = 'create'
ref.clearValidate()
this.handleSubjectManagement()
notifySuccess(this, '创建成功')
})
} else {
// 修改
putSubject(subjectInfo).then(() => {
this.resetTempSubject(parseInt(subjectInfo.serialNumber) + 1, subjectInfo.score)
this.dialogStatus = 'create'
ref.clearValidate()
this.handleSubjectManagement()
notifySuccess(this, '更新成功')
})
}
}
},
// 切换题目类型
changeSubjectType (value) {
console.log(value)
},
// 发布考试
handlePublic (row, status) {
......@@ -1054,84 +514,6 @@ export default {
notifySuccess(this, '更新成功')
})
},
// 批量删除
handleDeletesSubject () {
if (checkMultipleSelect(this.multipleSubjectSelection, this)) {
let ids = []
for (let i = 0; i < this.multipleSubjectSelection.length; i++) {
ids.push(this.multipleSubjectSelection[i].id)
}
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delAllSubject(ids).then(() => {
this.handleSubjectManagement()
notifySuccess(this, '删除成功')
})
}).catch(() => {})
}
},
// 导出
handleExportSubject () {
// 没选择题目,导出所有
if (this.multipleSubjectSelection.length === 0) {
this.$confirm('是否导出所有题目?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'success'
}).then(() => {
exportSubject([], this.subject.examinationId).then(response => {
// 导出Excel
exportExcel(response)
})
}).catch(() => {})
} else {
// 导出选中
this.$confirm('是否导出选中的题目?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'success'
}).then(() => {
let ids = []
for (let i = 0; i < this.multipleSubjectSelection.length; i++) {
ids.push(this.multipleSubjectSelection[i].id)
}
exportSubject(ids, '').then(response => {
// 导出Excel
exportExcel(response)
})
}).catch(() => {})
}
},
// 导入
handleImportSubject () {
this.dialogImportVisible = true
},
// 上传前
beforeUploadSubjectUpload (file) {
const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
const isLt10M = file.size / 1024 / 1024 < 10
if (!isExcel) {
this.$message.error('上传附件只能是 excel 格式!')
}
if (!isLt10M) {
this.$message.error('上传附件大小不能超过 10MB!')
}
return isExcel && isLt10M
},
handleUploadSubjectProgress (event, file, fileList) {
this.uploadingSubject = true
this.percentageSubject = parseInt(file.percentage.toFixed(0))
},
// 上传成功
handleUploadSubjectSuccess () {
this.dialogImportVisible = false
this.handleSubjectManagement()
notifySuccess(this, '导入成功')
this.uploadingSubject = false
},
// 图片上传前
beforeAvatarUpload (file) {
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
......@@ -1175,42 +557,6 @@ export default {
}
}
})
},
// 切换题型
handleTabChange (tab, event) {
this.tempSubject.type = parseInt(tab.name)
// 更新组件里的题目信息
this.updateComponentSubjectInfo()
},
updateCurrentTag (type) {
this.activeName = type + ''
},
resetActiveName () {
// 重置选项卡至单选题
this.activeName = '0'
},
// 更新组件里的题目信息
updateComponentSubjectInfo () {
// 单选题
const ref = this.getSubjectRef()
if (isNotEmpty(ref)) {
ref.setSubjectInfo(this.tempSubject)
}
},
getSubjectRef () {
let ref
switch (this.activeName) {
case '0':
ref = this.$refs['choices']
break
case '1':
ref = this.$refs['shortAnswer']
break
case '3':
ref = this.$refs['multipleChoices']
break
}
return ref
}
}
}
......
<template>
<div class="app-container">
<div class="filter-container">
<el-input :placeholder="$t('table.subjectName')" v-model="subject.listQuery.subjectName" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilterSubject"/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilterSubject">{{ $t('table.search') }}</el-button>
<el-button v-if="exam_btn_subject_add" class="filter-item" icon="el-icon-check" plain @click="handleCreateSubject">{{ $t('table.add') }}</el-button>
<el-button v-if="exam_btn_subject_add" class="filter-item" icon="el-icon-check" plain @click="handleCreateSubjectFromSubjectBank">{{ $t('table.addFromSubjectBank') }}</el-button>
<el-button v-if="exam_btn_subject_import" class="filter-item" icon="el-icon-upload2" plain @click="handleImportSubject">{{ $t('table.import') }}</el-button>
<el-button v-if="exam_btn_subject_export" class="filter-item" icon="el-icon-download" plain @click="handleExportSubject">{{ $t('table.export') }}</el-button>
</div>
<spinner-loading v-if="subject.listLoading"/>
<el-table
:data="subject.list"
highlight-current-row
style="width: 100%;"
@selection-change="handleSubjectSelectionChange"
@cell-dblclick="handleUpdateSubject"
@sort-change="sortSubjectChange">
<el-table-column type="selection" width="55"/>
<el-table-column :label="$t('table.subjectName')" min-width="120">
<template slot-scope="scope">
<span>{{ scope.row.subjectName | simpleStrFilter }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.type')" width="120">
<template slot-scope="scope">
<el-tag type="success">{{ scope.row.type | subjectTypeFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.score')" property="score" width="120">
<template slot-scope="scope">
<span>{{ scope.row.score }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.modifyDate')" property="updateTime" width="150">
<template slot-scope="scope">
<span>{{ scope.row.modifyDate | fmtDate('yyyy-MM-dd hh:mm') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.modifier')" property="modifier" width="120">
<template slot-scope="scope">
<span>{{ scope.row.modifier }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300px">
<template slot-scope="scope">
<el-button type="text" @click="handleViewSubject(scope.row)" icon="el-icon-view">{{ $t('table.view') }}</el-button>
<el-button v-if="exam_btn_subject" type="text" @click="handleUpdateSubject(scope.row)" icon="el-icon-edit">{{ $t('table.edit') }}</el-button>
<el-button v-if="exam_btn_del" type="text" @click="handleDeleteSubject(scope.row)" icon="el-icon-delete">{{ $t('table.delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination v-show="subject.total>0" :current-page="subject.listQuery.pageNum" :page-sizes="[10,20,30,50]" :page-size="subject.listQuery.pageSize" :total="subject.total" background layout="total, sizes, prev, pager, next, jumper" @size-change="handleSubjectSizeChange" @current-change="handleSubjectCurrentChange"/>
</div>
<!-- 导入题目 -->
<el-dialog :visible.sync="dialogImportVisible" :title="$t('table.import')">
<el-row>
<el-col :span="24">
<el-upload
drag
:multiple="false"
:auto-upload="true"
:show-file-list="true"
:before-upload="beforeUploadSubjectUpload"
:on-progress="handleUploadSubjectProgress"
:on-success="handleUploadSubjectSuccess"
:action="importUrl"
:headers="headers"
:data="params"
style="text-align: center;">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div slot="tip" class="el-upload__tip">只能上传xlsx文件</div>
</el-upload>
</el-col>
</el-row>
</el-dialog>
<!-- 题库列表 -->
<el-dialog title="选择题目" :visible.sync="category.dialogVisible" width="80%" top="10vh">
<el-row>
<el-col :span="4">
<el-card class="tree-box-card" style="margin-right: 5px;">
<div slot="header">
<span>题目分类</span>
</div>
<el-row>
<div class="tree-container">
<el-tree
:data="category.treeData"
:props="category.defaultProps"
class="filter-tree"
node-key="id"
highlight-current
accordion
@node-click="getNodeData"
/>
</div>
</el-row>
</el-card>
</el-col>
<el-col :span="20">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>选择题目</span>
</div>
<el-table
v-loading="category.listLoading"
:data="category.list"
:default-sort="{ prop: 'id', order: 'ascending' }"
highlight-current-row
style="width: 100%;"
@row-click = "handleSingleSubjectSelection"
@current-change="handleSingleSubjectCurrentChange">
<el-table-column align="center" width="55" label="" >
<template slot-scope="scope">
<el-radio :label="scope.$index" v-model="category.tempRadio" @change.native="handleSingleSubjectSelectionChange(scope.$index, scope.row)">&nbsp;</el-radio>
</template>
</el-table-column>
<el-table-column :label="$t('table.subjectName')" sortable prop="subject_name" property="subjectName" min-width="120">
<template slot-scope="scope">
<span>{{ scope.row.subjectName }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.type')">
<template slot-scope="scope">
<el-tag type="success">{{ scope.row.type | subjectTypeFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.score')">
<template slot-scope="scope">
<span>{{ scope.row.score }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.level')">
<template slot-scope="scope">
<el-rate v-model="scope.row.level" :max="4"/>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination v-show="category.total>0" :current-page="category.listQuery.pageNum" :page-sizes="[10,20,30, 50]" :page-size="category.listQuery.pageSize" :total="category.total" background layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange"/>
</div>
</el-card>
</el-col>
</el-row>
<div slot="footer" class="dialog-footer">
<el-button @click="category.dialogVisible = false">{{ $t('table.cancel') }}</el-button>
<el-button type="primary" @click="handleSelectSubject">{{ $t('table.confirm') }}</el-button>
</div>
</el-dialog>
<!-- 查看题目 -->
<el-dialog title="查看题目" :visible.sync="dialogViewVisible" width="60%" top="10vh">
<div class="subject-title">
<span class="subject-title-content" v-html="tempSubject.subjectName"/>
<span class="subject-title-content">&nbsp;({{tempSubject.score}})分</span>
</div>
<ul v-if="tempSubject.type === 0 || tempSubject.type === 3" class="subject-options">
<li class="subject-option" v-for="(option) in tempSubject.options">
<input class="toggle" type="checkbox">
<label><span class="subject-option-prefix">{{option.optionName}}&nbsp;</span><span v-html="option.optionContent" class="subject-option-prefix"></span></label>
</li>
</ul>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogViewVisible = false">{{ $t('table.confirm') }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { fetchSubjectListById } from '@/api/exam/exam'
import { getSubject, delSubject, exportSubject } from '@/api/exam/subject'
import { fetchCategoryTree } from '@/api/exam/subjectCategory'
import { getToken } from '@/utils/auth'
import waves from '@/directive/waves'
import { mapGetters } from 'vuex'
import { checkMultipleSelect, notifySuccess, messageSuccess, exportExcel, isNotEmpty } from '@/utils/util'
import SpinnerLoading from '@/components/SpinnerLoading'
export default {
name: 'ExamSubjectsManagement',
components: { SpinnerLoading },
directives: {
waves
},
data () {
return {
headers: {
Authorization: 'Bearer ' + getToken()
},
params: {
busiType: '1'
},
tableKey: 0,
list: null,
total: null,
listLoading: true,
listQuery: {
pageNum: 1,
pageSize: 10,
sort: 'id',
order: 'descending'
},
// 导入题目的url
importUrl: '/api/exam/v1/subject/import',
// 题目
subject: {
listQuery: {
pageNum: 1,
pageSize: 10,
examinationId: '',
categoryId: '',
sort: 'id',
order: 'ascending'
},
list: null,
total: null,
listLoading: true,
categoryId: ''
},
exam_btn_add: false,
exam_btn_edit: false,
exam_btn_del: false,
exam_btn_subject: false,
exam_btn_subject_add: false,
exam_btn_subject_del: false,
exam_btn_subject_import: false,
exam_btn_subject_export: false,
// 导入弹窗状态
dialogImportVisible: false,
// 预览弹窗状态
dialogViewVisible: false,
uploadingSubject: false,
dialogStatus: '',
textMap: {
update: '编辑',
create: '新建'
},
// 题目分类数据
category: {
dialogVisible: false,
// 题目列表查询参数
listQuery: {
subjectName: undefined,
categoryId: '',
sort: 'id',
order: 'ascending'
},
// 题目列表数据
list: [],
// 分类树数据
treeData: [],
// 题目分类数据
defaultProps: {
children: 'children',
label: 'categoryName'
},
// 列表加载状态
listLoading: false,
tempRadio: ''
},
// 多选题目
multipleSubjectSelection: [],
// 单选题目
singleSubjectSelection: [],
percentageSubject: '',
tempSubject: {
id: null,
examinationId: null,
categoryId: null,
subjectName: '',
type: 0,
choicesType: 0,
options: [
{ subjectChoicesId: '', optionName: 'A', optionContent: '' },
{ subjectChoicesId: '', optionName: 'B', optionContent: '' },
{ subjectChoicesId: '', optionName: 'C', optionContent: '' },
{ subjectChoicesId: '', optionName: 'D', optionContent: '' }
],
answer: {
subjectId: null,
answer: '',
answerType: '',
score: '',
type: 0
},
score: 5,
analysis: '',
level: 2
}
}
},
created () {
this.getList()
this.exam_btn_add = this.permissions['exam:exam:add']
this.exam_btn_edit = this.permissions['exam:exam:edit']
this.exam_btn_del = this.permissions['exam:exam:del']
this.exam_btn_subject = this.permissions['exam:exam:subject']
this.exam_btn_subject_add = this.permissions['exam:exam:subject:add']
this.exam_btn_subject_del = this.permissions['exam:exam:subject:del']
this.exam_btn_subject_import = this.permissions['exam:exam:subject:import']
this.exam_btn_subject_export = this.permissions['exam:exam:subject:export']
},
computed: {
...mapGetters([
'elements',
'permissions'
])
},
methods: {
handleSizeChange (val) {
this.listQuery.limit = val
this.getList()
},
handleCurrentChange (val) {
this.listQuery.pageNum = val
this.getList()
},
handleSubjectSizeChange (val) {
this.subject.listQuery.limit = val
this.getList()
},
handleSubjectCurrentChange (val) {
this.subject.listQuery.pageNum = val
this.getList()
},
sortSubjectChange (column, prop, order) {
this.subject.listQuery.sort = column.prop
this.subject.listQuery.order = column.order
this.getList()
},
handleFilterSubject () {
this.subject.listQuery.pageNum = 1
this.getList()
},
// 题库里选择题目
handleSingleSubjectSelectionChange (index, row) {
this.category.singleSubjectSelection = row
},
// 点击行时选择题目
handleSingleSubjectSelection (row) {
this.category.tempRadio = this.category.list.indexOf(row)
},
// 表格变化
handleSingleSubjectCurrentChange (row) {
this.category.singleSubjectSelection = row
},
// 选择题目
handleSelectSubject () {
// 加载题目信息
getSubject(this.category.singleSubjectSelection.id, { type: this.category.singleSubjectSelection.type }).then(response => {
this.tempSubject = response.data.data
// 隐藏弹框
this.category.dialogVisible = false
// 清空题目ID
this.tempSubject.id = ''
// 清空分类ID
this.tempSubject.categoryId = ''
// 清空选项ID
this.tempSubject.options.forEach(option => {
option.id = ''
})
// 绑定考试ID
this.tempSubject.examinationId = this.subject.examinationId
this.dialogStatus = 'create'
// 切换到对应的题型选项卡
this.updateCurrentTag(this.tempSubject.type)
// 更新组件里的题目信息
setTimeout(() => {
this.updateComponentSubjectInfo()
}, 200)
})
},
getList() {
this.subject.listLoading = true
this.subject.listQuery.examinationId = this.$route.params.id
fetchSubjectListById(this.subject.listQuery).then(response => {
if (response.data.list.length > 0) {
response.data.list.map(subject => {
subject.type = parseInt(subject.type)
subject.level = parseInt(subject.level)
})
this.subject.list = response.data.list
} else {
this.subject.list = []
}
this.subject.total = parseInt(response.data.total)
setTimeout(() => {
this.subject.listLoading = false
}, 500)
})
},
// 新建题目
handleCreateSubject () {
this.$router.push({
path: `/exam/${this.subject.listQuery.examinationId}/subjects/undefined/0`,
})
},
// 从题库新增
handleCreateSubjectFromSubjectBank () {
// 加载分类树
fetchCategoryTree(this.category.listQuery).then(response => {
this.category.treeData = response.data
})
this.category.dialogVisible = true
// 加载题目列表
},
// 导入
handleImportSubject () {
this.dialogImportVisible = true
},
// 导出
handleExportSubject () {
// 没选择题目,导出所有
if (this.multipleSubjectSelection.length === 0) {
this.$confirm('是否导出所有题目?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'success'
}).then(() => {
exportSubject([], this.subject.listQuery.examinationId).then(response => {
// 导出Excel
exportExcel(response)
})
}).catch(() => {})
} else {
// 导出选中
this.$confirm('是否导出选中的题目?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'success'
}).then(() => {
let ids = []
for (let i = 0; i < this.multipleSubjectSelection.length; i++) {
ids.push(this.multipleSubjectSelection[i].id)
}
exportSubject(ids, '').then(response => {
// 导出Excel
exportExcel(response)
})
}).catch(() => {})
}
},
handleSubjectSelectionChange (val) {
this.multipleSubjectSelection = val
},
// 修改题目
handleUpdateSubject (row) {
let examinationId = this.subject.listQuery.examinationId
this.$router.push({
path: `/exam/${examinationId}/subjects/${row.id}/${row.type}`,
})
},
// 查看题目
handleViewSubject (row) {
// 加载题目信息
getSubject(row.id, { type: row.type }).then(response => {
this.tempSubject = response.data.data
this.dialogViewVisible = true
})
},
// 删除题目
handleDeleteSubject (row) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delSubject(row.id, { type: row.type }).then(() => {
this.getList()
notifySuccess(this, '删除成功')
})
}).catch(() => {})
},
// 点击分类
getNodeData (data) {
// 获取分类ID
this.category.listQuery.categoryId = data.id
// 获取题目信息
this.getList()
},
handleUploadSubjectProgress (event, file, fileList) {
this.uploadingSubject = true
this.percentageSubject = parseInt(file.percentage.toFixed(0))
},
// 上传成功
handleUploadSubjectSuccess () {
this.dialogImportVisible = false
this.getList()
notifySuccess(this, '导入成功')
this.uploadingSubject = false
},
// 上传前
beforeUploadSubjectUpload (file) {
const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
const isLt10M = file.size / 1024 / 1024 < 10
if (!isExcel) {
this.$message.error('上传附件只能是 excel 格式!')
}
if (!isLt10M) {
this.$message.error('上传附件大小不能超过 10MB!')
}
return isExcel && isLt10M
},
}
}
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
/* 题目 */
.subject-title {
font-size: 18px;
line-height: 22px;
.subject-title-number {
display: inline-block;
line-height: 22px;
}
.subject-title-content {
display: inline-block;
}
}
.subject-options {
margin: 0;
padding: 0;
list-style: none;
> li {
position: relative;
font-size: 24px;
.toggle {
opacity: 0;
text-align: center;
width: 35px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none;
/* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.toggle+label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
background-size: 30px;
}
.toggle:checked+label {
background-size: 30px;
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}
label {
word-break: break-all;
padding: 10px 10px 10px 45px;
display: block;
line-height: 1.0;
transition: color 0.4s;
}
/* 选项名称 */
.subject-option-prefix {
font-size: 16px;
display: inline-block
}
}
}
</style>
<template>
<div class="app-container">
<el-tabs v-model="activeName" @tab-click="handleTabChange">
<!-- 单选题 -->
<el-tab-pane label="单选题" name="0" :disabled="tempSubject.type !== 0 && dialogStatus !== dialogStatusType.create">
<choices ref="choices" subjectInfo="tempSubject"></choices>
</el-tab-pane>
<!-- 多选题 -->
<el-tab-pane label="多选题" name="3" :disabled="tempSubject.type !== 3 && dialogStatus !== dialogStatusType.create">
<multiple-choices ref="multipleChoices" subjectInfo="tempSubject"></multiple-choices>
</el-tab-pane>
<!-- 简答题 -->
<el-tab-pane label="简答题" name="1" :disabled="tempSubject.type !== 1 && dialogStatus !== dialogStatusType.create">
<short-answer ref="shortAnswer" subjectInfo="tempSubject"></short-answer>
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer collapse-top">
<el-button @click="dialogSubjectFormVisible = false">{{ $t('table.cancel') }}</el-button>
<el-button v-if="dialogStatus === dialogStatusType.create" type="primary" @click="createSubjectData">{{ $t('table.save') }}</el-button>
<el-button v-else type="primary" @click="updateSubjectData">{{ $t('table.save') }}</el-button>
<el-button type="primary" @click="updateAndAddSubjectData">{{ $t('table.saveAndAdd') }}</el-button>
</div>
</div>
</template>
<script>
import waves from '@/directive/waves'
import { mapGetters } from 'vuex'
import { getSubject, addSubject, putSubject, delSubject, exportSubject } from '@/api/exam/subject'
import { notifySuccess, isNotEmpty, isCreate } from '@/utils/util'
import { dialogStatusConstant } from '@/utils/constant'
import SpinnerLoading from '@/components/SpinnerLoading'
import Tinymce from '@/components/Tinymce'
import Choices from '@/components/Subjects/Choices'
import MultipleChoices from '@/components/Subjects/MultipleChoices'
import ShortAnswer from '@/components/Subjects/ShortAnswer'
export default {
name: 'CourseManagement',
components: { Tinymce, SpinnerLoading, Choices, MultipleChoices, ShortAnswer },
directives: {
waves
},
data () {
return {
dialogSubjectFormVisible: false,
activeName: '0',
dialogStatus: '',
dialogStatusType: { ...dialogStatusConstant },
examinationId: '',
// 题目临时信息
tempSubject: {
id: '',
examinationId: '',
categoryId: 0,
subjectName: '',
type: 0,
choicesType: 0,
options: [
{ subjectChoicesId: '', optionName: 'A', optionContent: '' },
{ subjectChoicesId: '', optionName: 'B', optionContent: '' },
{ subjectChoicesId: '', optionName: 'C', optionContent: '' },
{ subjectChoicesId: '', optionName: 'D', optionContent: '' }
],
answer: {
subjectId: '',
answer: '',
answerType: '',
score: ''
},
score: 5,
analysis: '',
level: 2
},
}
},
created () {
this.getSubject()
},
computed: {
...mapGetters([
'elements',
'permissions'
])
},
methods: {
getSubject() {
let subjectId = this.$route.params.id
this.examinationId = this.$route.params.examinationId
if (isNotEmpty(subjectId)) {
// 加载选项信息
getSubject(subjectId, { type: this.$route.params.type }).then(response => {
const subjectInfo = response.data.data
this.tempSubject = subjectInfo
this.dialogStatus = dialogStatusConstant.update
// 切换到对应的题型选项卡
this.updateCurrentTag(subjectInfo.type)
setTimeout(() => {
const ref = this.getSubjectRef()
if (isNotEmpty(ref)) {
// 初始化单选题
this.$nextTick(() => {
ref.clearValidate()
ref.setSubjectInfo(subjectInfo)
})
}
}, 200)
})
} else {
// 新增题目
// 切换tab
this.dialogStatus = dialogStatusConstant.create
setTimeout(() => {
const ref = this.getSubjectRef()
if (isNotEmpty(ref)) {
this.$nextTick(() => {
// 初始化单选题选项
ref.initDefaultOptions()
})
}
}, 200)
}
},
// 切换题型
handleTabChange (tab, event) {
this.tempSubject.type = parseInt(tab.name)
// 更新组件里的题目信息
this.updateComponentSubjectInfo()
},
// 更新题目
updateSubjectData () {
const ref = this.getSubjectRef()
if (ref.validate()) {
const subjectInfo = ref.getSubjectInfo()
subjectInfo.examinationId = this.examinationId
putSubject(subjectInfo).then(() => {
this.dialogSubjectFormVisible = false
notifySuccess(this, '更新成功')
})
}
},
// 更新并添加题目
updateAndAddSubjectData () {
const ref = this.getSubjectRef()
if (ref.validate()) {
const subjectInfo = ref.getSubjectInfo()
// 绑定考试ID
subjectInfo.examinationId = this.examinationId
// 创建
if (isCreate(this.dialogStatus)) {
addSubject(subjectInfo).then(() => {
this.resetTempSubject(subjectInfo.score)
this.dialogStatus = dialogStatusConstant.create
ref.clearValidate()
this.getSubject()
notifySuccess(this, '创建成功')
})
} else {
// 修改
putSubject(subjectInfo).then(() => {
this.resetTempSubject(subjectInfo.score)
this.dialogStatus = dialogStatusConstant.create
ref.clearValidate()
notifySuccess(this, '更新成功')
})
}
}
},
updateCurrentTag (type) {
this.activeName = type + ''
},
// 更新组件里的题目信息
updateComponentSubjectInfo () {
// 单选题
const ref = this.getSubjectRef()
if (isNotEmpty(ref)) {
ref.setSubjectInfo(this.tempSubject)
}
},
getSubjectRef () {
let ref
switch (this.activeName) {
case '0':
ref = this.$refs['choices']
break
case '1':
ref = this.$refs['shortAnswer']
break
case '3':
ref = this.$refs['multipleChoices']
break
}
return ref
},
// 保存题目
createSubjectData () {
const ref = this.getSubjectRef()
if (ref.validate()) {
let subjectInfo = ref.getSubjectInfo()
// 绑定考试ID
subjectInfo.examinationId = this.examinationId
addSubject(subjectInfo).then(() => {
this.dialogSubjectFormVisible = false
this.getSubject()
notifySuccess(this, '创建成功')
})
}
},
// 切换题目类型
changeSubjectType (value) {
console.log(value)
},
resetActiveName () {
// 重置选项卡至单选题
this.activeName = '0'
},
resetTempSubject (score) {
const ref = this.getSubjectRef()
if (isNotEmpty(ref)) {
ref.resetTempSubject(score)
}
}
}
}
</script>
<style lang="scss" scoped>
.collapse-top {
margin-top: 20px;
}
</style>
......@@ -57,10 +57,11 @@ public class PreviewFilter implements GlobalFilter, Ordered {
private boolean shouldFilter(ServerHttpRequest request) {
// enabled不为true
Map<String, String> previewConfigMap = LoadingCacheHelper.getInstance().get(PreviewConfigLoader.class, PreviewConfigLoader.PREVIEW_ENABLE);
if (previewConfigMap == null || previewConfigMap.isEmpty() || !"true".equals(previewConfigMap.get(PreviewConfigLoader.PREVIEW_ENABLE)))
return false;
if (previewConfigMap == null || previewConfigMap.isEmpty() || !previewConfigMap.containsKey(PreviewConfigLoader.PREVIEW_ENABLE)) {
return true;
}
// 演示环境下,只拦截对默认租户的修改操作
if (GatewayConstant.DEFAULT_TENANT_CODE
if ("true".equals(previewConfigMap.get(PreviewConfigLoader.PREVIEW_ENABLE)) && GatewayConstant.DEFAULT_TENANT_CODE
.equals(request.getHeaders().getFirst(GatewayConstant.TENANT_CODE_HEADER))) {
String method = request.getMethodValue(), uri = request.getURI().getPath();
// GET请求、POST请求
......
package com.github.tangyi.exam.enums;
import com.github.tangyi.exam.service.BaseSubjectService;
import com.github.tangyi.exam.service.ISubjectService;
import com.github.tangyi.exam.service.SubjectChoicesService;
import com.github.tangyi.exam.service.SubjectJudgementService;
import com.github.tangyi.exam.service.SubjectShortAnswerService;
......@@ -29,7 +29,7 @@ public enum SubjectTypeEnum {
private Integer value;
private Class<? extends BaseSubjectService> service;
private Class<? extends ISubjectService> service;
/**
* 根据类型返回具体的SubjectType
......
package com.github.tangyi.exam.excel.listener;
import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.common.core.utils.excel.AbstractExcelImportListener;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.api.module.SubjectOption;
import com.github.tangyi.exam.excel.model.SubjectExcelModel;
import com.github.tangyi.exam.service.SubjectService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import java.util.ArrayList;
......@@ -30,11 +34,47 @@ public class SubjectImportListener extends AbstractExcelImportListener<SubjectEx
@Override
public void saveData(List<SubjectExcelModel> subjectExcelModelList) {
logger.info("saveData size: {}", subjectExcelModelList.size());
logger.info("SaveData size: {}", subjectExcelModelList.size());
List<SubjectDto> subjects = new ArrayList<>();
String creator = SysUtil.getUser();
String sysCode = SysUtil.getSysCode();
String tenantCode = SysUtil.getTenantCode();
subjectExcelModelList.forEach(subject -> {
SubjectDto subjectDto = new SubjectDto();
subjectDto.setCommonValue(creator, sysCode, tenantCode);
BeanUtils.copyProperties(subject, subjectDto);
List<SubjectOption> subjectOptions = new ArrayList<>();
if (StringUtils.isNotBlank(subject.getOptions())) {
String[] options = subject.getOptions().split("\\$\\$");
// $$A# 测试测试
for (String option : options) {
if (StringUtils.isNotBlank(option)) {
String[] optionInfos = option.split("#");
if (optionInfos.length >= 2) {
// 去掉$$
String optionName = optionInfos[0].trim();
StringBuilder optionContent = new StringBuilder();
if (optionInfos.length > 2) {
for (int i = 1; i < optionInfos.length; i++) {
optionContent.append(optionInfos[i].trim());
}
} else {
optionContent = new StringBuilder(optionInfos[1].trim());
}
SubjectOption subjectOption = new SubjectOption();
subjectOption.setOptionName(optionName);
subjectOption.setOptionContent(optionContent.toString());
subjectOptions.add(subjectOption);
}
}
}
}
subjectDto.setOptions(subjectOptions);
// 答案
Answer answer = new Answer();
answer.setAnswer(subject.getAnswer());
subjectDto.setAnswer(answer);
subjects.add(subjectDto);
});
subjectService.importSubject(subjects, examinationId, categoryId);
......
......@@ -20,7 +20,7 @@ import java.util.Date;
*/
@Data
@ExcelModel("考试记录")
@ContentRowHeight(10)
@ContentRowHeight(18)
@HeadRowHeight(20)
@ColumnWidth(25)
public class ExamRecordExcelModel {
......
......@@ -18,7 +18,7 @@ import lombok.Data;
*/
@Data
@ExcelModel("题目信息")
@ContentRowHeight(10)
@ContentRowHeight(18)
@HeadRowHeight(20)
@ColumnWidth(25)
public class SubjectExcelModel {
......@@ -45,9 +45,15 @@ public class SubjectExcelModel {
@NumberFormat("#.##")
private Double score;
@ExcelProperty("答案")
private String answer;
@ExcelProperty("解析")
private String analysis;
@ExcelProperty(value = "难度等级", converter = SubjectLevelConverter.class)
private Integer level;
@ExcelProperty("选项")
private String options;
}
package com.github.tangyi.exam.handler;
import com.github.tangyi.common.core.utils.SpringContextHolder;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.service.SubjectService;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 统计成绩
* @author tangyi
* @date 2020/1/19 10:07 上午
*/
public abstract class AbstractAnswerHandler implements IAnswerHandler {
@Override
public AnswerHandleResult handle(List<Answer> answers) {
if (CollectionUtils.isNotEmpty(answers)) {
// 保存答题正确的题目分数
List<Double> rightScore = new ArrayList<>();
// 获取题目信息
List<SubjectDto> subjects = getSubjects(answers);
answers.forEach(tempAnswer -> {
subjects.stream()
// 题目ID匹配
.filter(tempSubject -> tempSubject.getId().equals(tempAnswer.getSubjectId())).findFirst()
.ifPresent(subject -> judge(tempAnswer, subject, rightScore));
});
AnswerHandleResult result = new AnswerHandleResult();
// 记录总分、正确题目数、错误题目数
result.setScore(rightScore.stream().mapToDouble(Double::valueOf).sum());
result.setCorrectNum(rightScore.size());
result.setInCorrectNum(answers.size() - rightScore.size());
return result;
}
return null;
}
@Override
public List<SubjectDto> getSubjects(List<Answer> answers) {
return SpringContextHolder.getApplicationContext().getBean(SubjectService.class)
.findListById(getSubjectType().getValue(),
answers.stream().map(Answer::getSubjectId).distinct().toArray(Long[]::new));
}
}
......@@ -8,7 +8,7 @@ import lombok.Data;
* @date 2019/12/8 9:56 下午
*/
@Data
public class HandleResult {
public class AnswerHandleResult {
/**
* 总分
......
package com.github.tangyi.exam.handler;
import com.github.tangyi.exam.api.module.Answer;
import java.util.List;
/**
* 统计成绩
* @author tangyi
* @date 2019/12/8 9:56 下午
*/
@FunctionalInterface
public interface BaseHandler {
HandleResult handle(List<Answer> answers);
}
package com.github.tangyi.exam.handler;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.enums.SubjectTypeEnum;
import java.util.List;
/**
* 统计成绩
* @author tangyi
* @date 2019/12/8 9:56 下午
*/
public interface IAnswerHandler {
/**
* 统计成绩
* @param answers answers
* @return HandleResult
*/
AnswerHandleResult handle(List<Answer> answers);
/**
* 获取题目类型
* @return SubjectTypeEnum
*/
SubjectTypeEnum getSubjectType();
/**
* 获取题目列表
* @param answers answers
* @return List
*/
List<SubjectDto> getSubjects(List<Answer> answers);
/**
* 判断是否正确
* @param answer answer
* @param subject subject
* @param rightScore rightScore
*/
void judge(Answer answer, SubjectDto subject, List<Double> rightScore);
}
package com.github.tangyi.exam.handler.impl;
import com.github.tangyi.exam.api.constants.AnswerConstant;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.enums.SubjectTypeEnum;
import com.github.tangyi.exam.handler.AbstractAnswerHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 选择题
* @author tangyi
* @date 2019/12/8 21:57
*/
@Slf4j
@AllArgsConstructor
@Component
public class ChoicesAnswerHandler extends AbstractAnswerHandler {
@Override
public SubjectTypeEnum getSubjectType() {
return SubjectTypeEnum.CHOICES;
}
@Override
public void judge(Answer answer, SubjectDto subject, List<Double> rightScore) {
if (subject.getAnswer().getAnswer().equalsIgnoreCase(answer.getAnswer())) {
rightScore.add(subject.getScore());
answer.setAnswerType(AnswerConstant.RIGHT);
answer.setScore(subject.getScore());
answer.setMarkStatus(AnswerConstant.MARKED);
} else {
answer.setAnswerType(AnswerConstant.WRONG);
answer.setScore(0.0);
answer.setMarkStatus(AnswerConstant.MARKED);
}
}
}
package com.github.tangyi.exam.handler.impl;
import com.github.tangyi.exam.api.constants.AnswerConstant;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.enums.SubjectTypeEnum;
import com.github.tangyi.exam.handler.BaseHandler;
import com.github.tangyi.exam.handler.HandleResult;
import com.github.tangyi.exam.service.SubjectService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 选择题
* @author tangyi
* @date 2019/12/8 21:57
*/
@Slf4j
@AllArgsConstructor
@Component
public class ChoicesHandler implements BaseHandler {
private final SubjectService subjectService;
@Override
public HandleResult handle(List<Answer> answers) {
if (CollectionUtils.isEmpty(answers))
return null;
// 查找题目信息
java.util.List<SubjectDto> subjects = subjectService.findListById(SubjectTypeEnum.CHOICES.getValue(),
answers.stream().map(Answer::getSubjectId).distinct().toArray(Long[]::new));
// 保存答题正确的题目分数
List<Double> rightScore = new ArrayList<>();
answers.forEach(tempAnswer -> {
// 题目集合
subjects.stream()
// 题目ID、题目答案匹配
.filter(tempSubject -> tempSubject.getId().equals(tempAnswer.getSubjectId()) && tempSubject
.getAnswer().getAnswer().equalsIgnoreCase(tempAnswer.getAnswer()))
// 记录答题正确的成绩
.findFirst().ifPresent(right -> {
rightScore.add(right.getScore());
tempAnswer.setAnswerType(AnswerConstant.RIGHT);
tempAnswer.setScore(right.getScore());
tempAnswer.setMarkStatus(AnswerConstant.MARKED);
});
});
// 统计错题
answers.forEach(tempAnswer -> {
// 题目集合
subjects.stream()
// 题目ID、题目答案匹配
.filter(tempSubject -> tempSubject.getId().equals(tempAnswer.getSubjectId()) && !tempSubject
.getAnswer().getAnswer().equalsIgnoreCase(tempAnswer.getAnswer()))
// 错题
.findFirst().ifPresent(tempSubject -> {
tempAnswer.setAnswerType(AnswerConstant.WRONG);
tempAnswer.setScore(0.0);
tempAnswer.setMarkStatus(AnswerConstant.MARKED);
});
});
HandleResult result = new HandleResult();
// 记录总分、正确题目数、错误题目数
result.setScore(rightScore.stream().mapToDouble(Double::valueOf).sum());
result.setCorrectNum(rightScore.size());
result.setInCorrectNum(answers.size() - rightScore.size());
return result;
}
}
package com.github.tangyi.exam.handler.impl;
import com.github.tangyi.exam.api.constants.AnswerConstant;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.enums.SubjectTypeEnum;
import com.github.tangyi.exam.handler.AbstractAnswerHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 判断题
* @author tangyi
* @date 2019/12/8 22:01
*/
@Slf4j
@AllArgsConstructor
@Component
public class JudgementAnswerHandler extends AbstractAnswerHandler {
@Override
public SubjectTypeEnum getSubjectType() {
return SubjectTypeEnum.JUDGEMENT;
}
@Override
public void judge(Answer answer, SubjectDto subject, List<Double> rightScore) {
if (subject.getAnswer().getAnswer().equalsIgnoreCase(answer.getAnswer())) {
rightScore.add(subject.getScore());
answer.setAnswerType(AnswerConstant.RIGHT);
answer.setScore(subject.getScore());
answer.setMarkStatus(AnswerConstant.MARKED);
} else {
answer.setAnswerType(AnswerConstant.WRONG);
answer.setScore(0.0);
answer.setMarkStatus(AnswerConstant.MARKED);
}
}
}
package com.github.tangyi.exam.handler.impl;
import com.github.tangyi.exam.api.constants.AnswerConstant;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.enums.SubjectTypeEnum;
import com.github.tangyi.exam.handler.BaseHandler;
import com.github.tangyi.exam.handler.HandleResult;
import com.github.tangyi.exam.service.SubjectService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 判断题
* @author tangyi
* @date 2019/12/8 22:01
*/
@Slf4j
@AllArgsConstructor
@Component
public class JudgementHandler implements BaseHandler {
private final SubjectService subjectService;
@Override
public HandleResult handle(List<Answer> answers) {
if (CollectionUtils.isEmpty(answers))
return null;
// 查找题目信息
List<SubjectDto> subjects = subjectService.findListById(SubjectTypeEnum.JUDGEMENT.getValue(),
answers.stream().map(Answer::getSubjectId).distinct().toArray(Long[]::new));
// 保存答题正确的题目分数
List<Double> rightScore = new ArrayList<>();
answers.forEach(tempAnswer -> {
// 题目集合
subjects.stream()
// 题目ID、题目答案匹配
.filter(tempSubject -> tempSubject.getId().equals(tempAnswer.getSubjectId()) && tempSubject
.getAnswer().getAnswer().equalsIgnoreCase(tempAnswer.getAnswer()))
// 记录答题正确的成绩
.findFirst().ifPresent(right -> {
rightScore.add(right.getScore());
tempAnswer.setAnswerType(AnswerConstant.RIGHT);
tempAnswer.setScore(right.getScore());
tempAnswer.setMarkStatus(AnswerConstant.MARKED);
});
});
// 统计错题
answers.forEach(tempAnswer -> {
// 题目集合
subjects.stream()
// 题目ID、题目答案匹配
.filter(tempSubject -> tempSubject.getId().equals(tempAnswer.getSubjectId()) && !tempSubject
.getAnswer().getAnswer().equalsIgnoreCase(tempAnswer.getAnswer()))
// 错题
.findFirst().ifPresent(tempSubject -> {
tempAnswer.setAnswerType(AnswerConstant.WRONG);
tempAnswer.setScore(0.0);
tempAnswer.setMarkStatus(AnswerConstant.MARKED);
});
});
HandleResult result = new HandleResult();
// 记录总分、正确题目数、错误题目数
result.setScore(rightScore.stream().mapToDouble(Double::valueOf).sum());
result.setCorrectNum(rightScore.size());
result.setInCorrectNum(answers.size() - rightScore.size());
return result;
}
}
package com.github.tangyi.exam.handler.impl;
import com.github.tangyi.exam.api.constants.AnswerConstant;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.enums.SubjectTypeEnum;
import com.github.tangyi.exam.handler.AbstractAnswerHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 统计多选题
* @author tangyi
* @date 2020/1/19 10:02 上午
*/
@Slf4j
@AllArgsConstructor
@Component
public class MultipleChoicesAnswerHandler extends AbstractAnswerHandler {
@Override
public SubjectTypeEnum getSubjectType() {
return SubjectTypeEnum.MULTIPLE_CHOICES;
}
@Override
public void judge(Answer answer, SubjectDto subject, List<Double> rightScore) {
if (StringUtils.isNotBlank(subject.getAnswer().getAnswer())) {
boolean isRight = true;
String[] standerAnswers = subject.getAnswer().getAnswer().split(",");
for (String as : answer.getAnswer().split(",")) {
if (!ArrayUtils.contains(standerAnswers, as)) {
isRight = false;
}
}
if (isRight) {
rightScore.add(subject.getScore());
answer.setAnswerType(AnswerConstant.RIGHT);
answer.setScore(subject.getScore());
answer.setMarkStatus(AnswerConstant.MARKED);
} else {
answer.setAnswerType(AnswerConstant.WRONG);
answer.setScore(0.0);
answer.setMarkStatus(AnswerConstant.MARKED);
}
}
}
}
package com.github.tangyi.exam.handler.impl;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.handler.BaseHandler;
import com.github.tangyi.exam.handler.HandleResult;
import com.github.tangyi.exam.service.SubjectService;
import com.github.tangyi.exam.enums.SubjectTypeEnum;
import com.github.tangyi.exam.handler.AbstractAnswerHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
......@@ -18,12 +18,20 @@ import java.util.List;
@Slf4j
@AllArgsConstructor
@Component
public class ShortAnswerHandler implements BaseHandler {
public class ShortAnswerHandler extends AbstractAnswerHandler {
private final SubjectService subjectService;
@Override
public SubjectTypeEnum getSubjectType() {
return SubjectTypeEnum.SHORT_ANSWER;
}
@Override
public HandleResult handle(List<Answer> answers) {
public List<SubjectDto> getSubjects(List<Answer> answers) {
return null;
}
@Override
public void judge(Answer answer, SubjectDto subject, List<Double> rightScore) {
// TODO
}
}
......@@ -22,12 +22,13 @@ import com.github.tangyi.exam.api.module.Examination;
import com.github.tangyi.exam.api.module.ExaminationRecord;
import com.github.tangyi.exam.api.module.ExaminationSubject;
import com.github.tangyi.exam.enums.SubjectTypeEnum;
import com.github.tangyi.exam.handler.HandleResult;
import com.github.tangyi.exam.handler.impl.ChoicesHandler;
import com.github.tangyi.exam.handler.impl.JudgementHandler;
import com.github.tangyi.exam.handler.AnswerHandleResult;
import com.github.tangyi.exam.handler.impl.ChoicesAnswerHandler;
import com.github.tangyi.exam.handler.impl.JudgementAnswerHandler;
import com.github.tangyi.exam.handler.impl.MultipleChoicesAnswerHandler;
import com.github.tangyi.exam.mapper.AnswerMapper;
import com.github.tangyi.exam.utils.ExamRecordUtil;
import com.github.tangyi.exam.utils.HandlerUtil;
import com.github.tangyi.exam.utils.AnswerHandlerUtil;
import com.github.tangyi.user.api.feign.UserServiceClient;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
......@@ -67,9 +68,11 @@ public class AnswerService extends CrudService<AnswerMapper, Answer> {
private final ExaminationSubjectService examinationSubjectService;
private final ChoicesHandler choicesHandler;
private final ChoicesAnswerHandler choicesHandler;
private final JudgementHandler judgementHandler;
private final MultipleChoicesAnswerHandler multipleChoicesHandler;
private final JudgementAnswerHandler judgementHandler;
private final RedisTemplate<String, String> redisTemplate;
......@@ -200,7 +203,6 @@ public class AnswerService extends CrudService<AnswerMapper, Answer> {
* 提交答卷,自动统计选择题得分
*
* @param answer answer
* @return boolean
* @author tangyi
* @date 2018/12/26 14:09
*/
......@@ -217,10 +219,10 @@ public class AnswerService extends CrudService<AnswerMapper, Answer> {
// 分类题目
Map<String, List<Answer>> distinctAnswer = this.distinctAnswer(answerList);
// 暂时只自动统计单选题、多选题、判断题,简答题由老师阅卷批改
HandleResult choiceResult = choicesHandler.handle(distinctAnswer.get(SubjectTypeEnum.CHOICES.name()));
HandleResult multipleResult = choicesHandler.handle(distinctAnswer.get(SubjectTypeEnum.MULTIPLE_CHOICES.name()));
HandleResult judgementResult = judgementHandler.handle(distinctAnswer.get(SubjectTypeEnum.JUDGEMENT.name()));
HandleResult result = HandlerUtil.addAll(Arrays.asList(choiceResult, multipleResult, judgementResult));
AnswerHandleResult choiceResult = choicesHandler.handle(distinctAnswer.get(SubjectTypeEnum.CHOICES.name()));
AnswerHandleResult multipleResult = multipleChoicesHandler.handle(distinctAnswer.get(SubjectTypeEnum.MULTIPLE_CHOICES.name()));
AnswerHandleResult judgementResult = judgementHandler.handle(distinctAnswer.get(SubjectTypeEnum.JUDGEMENT.name()));
AnswerHandleResult result = AnswerHandlerUtil.addAll(Arrays.asList(choiceResult, multipleResult, judgementResult));
// 记录总分、正确题目数、错误题目数
record.setScore(result.getScore());
record.setCorrectNumber(result.getCorrectNum());
......
......@@ -11,7 +11,7 @@ import java.util.List;
* @author tangyi
* @date 2019/6/16 17:30
*/
public interface BaseSubjectService {
public interface ISubjectService {
/**
* 根据ID查询
......
......@@ -3,6 +3,7 @@ package com.github.tangyi.exam.service;
import com.github.pagehelper.PageInfo;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.service.CrudService;
import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.exam.api.constants.AnswerConstant;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.ExaminationSubject;
......@@ -29,7 +30,7 @@ import java.util.List;
@AllArgsConstructor
@Service
public class SubjectChoicesService extends CrudService<SubjectChoicesMapper, SubjectChoices>
implements BaseSubjectService {
implements ISubjectService {
private final SubjectOptionService subjectOptionService;
......@@ -96,20 +97,7 @@ public class SubjectChoicesService extends CrudService<SubjectChoicesMapper, Sub
@CacheEvict(value = "subjectChoices", key = "#subjectChoices.id")
public int update(SubjectChoices subjectChoices) {
// 更新选项
List<SubjectOption> options = subjectChoices.getOptions();
if (CollectionUtils.isNotEmpty(options)) {
SubjectOption subjectOption = new SubjectOption();
subjectOption.setSubjectChoicesId(subjectChoices.getId());
subjectOptionService.deleteBySubjectChoicesId(subjectOption);
// 初始化
options.forEach(option -> {
option.setCommonValue(subjectChoices.getCreator(), subjectChoices.getApplicationCode(),
subjectChoices.getTenantCode());
option.setSubjectChoicesId(subjectChoices.getId());
});
// 批量插入
subjectOptionService.insertBatch(options);
}
this.insertOptions(subjectChoices);
return super.update(subjectChoices);
}
......@@ -305,10 +293,34 @@ public class SubjectChoicesService extends CrudService<SubjectChoicesMapper, Sub
BeanUtils.copyProperties(subjectDto, subjectChoices);
subjectChoices.setAnswer(subjectDto.getAnswer().getAnswer());
subjectChoices.setChoicesType(subjectDto.getType());
insertOptions(subjectChoices);
return this.insert(subjectChoices);
}
/**
* 保存选项
* @param subjectChoices subjectChoices
* @author tangyi
* @date 2020/01/17 22:30:48
*/
@Transactional
public void insertOptions(SubjectChoices subjectChoices) {
if (CollectionUtils.isNotEmpty(subjectChoices.getOptions())) {
SubjectOption subjectOption = new SubjectOption();
subjectOption.setSubjectChoicesId(subjectChoices.getId());
subjectOptionService.deleteBySubjectChoicesId(subjectOption);
// 初始化
subjectChoices.getOptions().forEach(option -> {
option.setCommonValue(subjectChoices.getCreator(), subjectChoices.getApplicationCode(),
subjectChoices.getTenantCode());
option.setSubjectChoicesId(subjectChoices.getId());
});
// 批量插入
subjectOptionService.insertBatch(subjectChoices.getOptions());
}
}
/**
* 更新,包括更新选项
*
* @param subjectDto subjectDto
......@@ -321,6 +333,7 @@ public class SubjectChoicesService extends CrudService<SubjectChoicesMapper, Sub
public int updateSubject(SubjectDto subjectDto) {
SubjectChoices subjectChoices = new SubjectChoices();
BeanUtils.copyProperties(subjectDto, subjectChoices);
subjectChoices.setCommonValue(SysUtil.getUser(), SysUtil.getSysCode(), SysUtil.getTenantCode());
// 参考答案
subjectChoices.setAnswer(subjectDto.getAnswer().getAnswer());
return this.update(subjectChoices);
......
......@@ -21,7 +21,7 @@ import java.util.List;
@Slf4j
@Service
public class SubjectJudgementService extends CrudService<SubjectJudgementMapper, SubjectJudgement>
implements BaseSubjectService {
implements ISubjectService {
/**
* 根据ID查询
......
......@@ -190,17 +190,6 @@ public class SubjectService {
examinationSubject.setSubjectId(subjectDto.getId());
examinationSubject.setType(subjectDto.getType());
examinationSubjectService.insert(examinationSubject);
// 保存选项
if (CollectionUtils.isNotEmpty(subjectDto.getOptions())) {
// 初始化基本属性
subjectDto.getOptions().forEach(subjectOption -> {
subjectOption.setCommonValue(subjectDto.getCreator(), subjectDto.getApplicationCode(),
subjectDto.getTenantCode());
subjectOption.setSubjectChoicesId(subjectDto.getId());
});
// 批量保存
subjectOptionService.insertBatch(subjectDto.getOptions());
}
return subjectService(subjectDto.getType()).insertSubject(subjectDto);
}
......@@ -214,7 +203,10 @@ public class SubjectService {
*/
@Transactional
public int update(SubjectDto subjectDto) {
return subjectService(subjectDto.getType()).updateSubject(subjectDto);
int update;
if ((update = subjectService(subjectDto.getType()).updateSubject(subjectDto)) == 0)
update = this.insert(subjectDto);
return update;
}
/**
......@@ -293,7 +285,7 @@ public class SubjectService {
* @author tangyi
* @date 2019/06/16 17:34
*/
private BaseSubjectService subjectService(Integer type) {
private ISubjectService subjectService(Integer type) {
return SpringContextHolder.getApplicationContext().getBean(SubjectTypeEnum.matchByValue(type).getService());
}
......@@ -337,7 +329,7 @@ public class SubjectService {
* @author tangyi
* @date 2019/06/17 10:43
*/
public Map<String, Long[]> getSubjectIdByType(List<ExaminationSubject> examinationSubjects) {
private Map<String, Long[]> getSubjectIdByType(List<ExaminationSubject> examinationSubjects) {
Map<String, Long[]> idMap = new HashMap<>();
examinationSubjects.stream().collect(Collectors.groupingBy(ExaminationSubject::getType, Collectors.toList()))
.forEach((type, temp) -> {
......@@ -350,6 +342,11 @@ public class SubjectService {
temp.stream().map(ExaminationSubject::getSubjectId).distinct()
.toArray(Long[]::new));
break;
case MULTIPLE_CHOICES:
idMap.put(SubjectTypeEnum.MULTIPLE_CHOICES.name(),
temp.stream().map(ExaminationSubject::getSubjectId).distinct()
.toArray(Long[]::new));
break;
case SHORT_ANSWER:
idMap.put(SubjectTypeEnum.SHORT_ANSWER.name(),
temp.stream().map(ExaminationSubject::getSubjectId).distinct()
......@@ -396,6 +393,18 @@ public class SubjectService {
subjectDtoList.addAll(SubjectUtil.subjectChoicesToDto(subjectChoicesList));
}
}
if (idMap.containsKey(SubjectTypeEnum.MULTIPLE_CHOICES.name())) {
List<SubjectChoices> subjectChoicesList = subjectChoicesService.findListById(idMap.get(SubjectTypeEnum.MULTIPLE_CHOICES.name()));
if (CollectionUtils.isNotEmpty(subjectChoicesList)) {
// 查找选项信息
if (findOptions) {
subjectChoicesList = subjectChoicesList.stream().map(subjectChoicesService::get)
.collect(Collectors.toList());
}
subjectDtoList.addAll(SubjectUtil.subjectChoicesToDto(subjectChoicesList));
}
}
if (idMap.containsKey(SubjectTypeEnum.SHORT_ANSWER.name())) {
List<SubjectShortAnswer> subjectShortAnswers = subjectShortAnswerService.findListById(idMap.get(SubjectTypeEnum.SHORT_ANSWER.name()));
if (CollectionUtils.isNotEmpty(subjectShortAnswers)) {
......
......@@ -24,7 +24,7 @@ import java.util.List;
*/
@Service
public class SubjectShortAnswerService extends CrudService<SubjectShortAnswerMapper, SubjectShortAnswer>
implements BaseSubjectService {
implements ISubjectService {
/**
* 查找题目
......
package com.github.tangyi.exam.utils;
import com.github.tangyi.exam.handler.HandleResult;
import com.github.tangyi.exam.handler.AnswerHandleResult;
import java.util.List;
......@@ -9,14 +9,14 @@ import java.util.List;
* @author tangyi
* @date 2019/12/8 22:42
*/
public class HandlerUtil {
public class AnswerHandlerUtil {
public static HandleResult addAll(List<HandleResult> results) {
HandleResult result = new HandleResult();
public static AnswerHandleResult addAll(List<AnswerHandleResult> results) {
AnswerHandleResult result = new AnswerHandleResult();
int score = 0;
int correctNum = 0;
int inCorrectNum = 0;
for (HandleResult tempResult : results) {
for (AnswerHandleResult tempResult : results) {
if (tempResult != null) {
score += tempResult.getScore();
correctNum += tempResult.getCorrectNum();
......
......@@ -7,6 +7,7 @@ import com.github.tangyi.exam.api.module.SubjectShortAnswer;
import com.github.tangyi.exam.enums.SubjectTypeEnum;
import com.github.tangyi.exam.excel.model.SubjectExcelModel;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import java.util.ArrayList;
......@@ -23,114 +24,124 @@ import java.util.stream.Collectors;
*/
public class SubjectUtil {
private SubjectUtil() {
}
private SubjectUtil() {
}
/**
* 转换对象
* @param subjectDtoList subjectDtoList
* @return List
*/
public static List<SubjectExcelModel> convertToExcelModel(List<SubjectDto> subjectDtoList) {
List<SubjectExcelModel> subjectExcelModels = new ArrayList<>(subjectDtoList.size());
subjectDtoList.forEach(subject -> {
SubjectExcelModel subjectExcelModel = new SubjectExcelModel();
BeanUtils.copyProperties(subject, subjectExcelModel);
subjectExcelModels.add(subjectExcelModel);
});
return subjectExcelModels;
}
/**
* 转换对象
*
* @param subjectDtoList subjectDtoList
* @return List
*/
public static List<SubjectExcelModel> convertToExcelModel(List<SubjectDto> subjectDtoList) {
List<SubjectExcelModel> subjectExcelModels = new ArrayList<>(subjectDtoList.size());
subjectDtoList.forEach(subject -> {
SubjectExcelModel subjectExcelModel = new SubjectExcelModel();
BeanUtils.copyProperties(subject, subjectExcelModel);
if (CollectionUtils.isNotEmpty(subject.getOptions())) {
List<String> optionString = subject.getOptions().stream()
.map(option -> "$$" + option.getOptionName() + "# " + option.getOptionContent()).collect(Collectors.toList());
subjectExcelModel.setOptions(StringUtils.join(optionString, "\n"));
}
subjectExcelModel.setAnswer(subject.getAnswer().getAnswer());
subjectExcelModels.add(subjectExcelModel);
});
return subjectExcelModels;
}
/**
* SubjectChoices转SubjectDto
*
* @param subjectChoice subjectChoice
* @return List
* @author tangyi
* @date 2019/06/16 16:50
*/
public static SubjectDto subjectChoicesToDto(SubjectChoices subjectChoice) {
if (subjectChoice == null)
return null;
SubjectDto subjectDto = new SubjectDto();
subjectDto.setId(subjectChoice.getId());
subjectDto.setSubjectName(subjectChoice.getSubjectName());
subjectDto.setScore(subjectChoice.getScore());
subjectDto.setAnalysis(subjectChoice.getAnalysis());
subjectDto.setLevel(subjectChoice.getLevel());
// 选择题类型匹配
SubjectTypeEnum subjectTypeEnum = SubjectTypeEnum.matchByValue(subjectChoice.getChoicesType());
if (subjectTypeEnum != null)
subjectDto.setType(subjectTypeEnum.getValue());
subjectDto.setChoicesType(subjectChoice.getChoicesType());
subjectDto.setOptions(subjectChoice.getOptions());
subjectDto.setCreator(subjectChoice.getCreator());
// 参考答案
Answer answer = new Answer();
answer.setAnswer(subjectChoice.getAnswer());
subjectDto.setAnswer(answer);
return subjectDto;
}
/**
* SubjectChoices转SubjectDto
*
* @param subjectChoice subjectChoice
* @return List
* @author tangyi
* @date 2019/06/16 16:50
*/
public static SubjectDto subjectChoicesToDto(SubjectChoices subjectChoice) {
if (subjectChoice == null)
return null;
SubjectDto subjectDto = new SubjectDto();
subjectDto.setId(subjectChoice.getId());
subjectDto.setSubjectName(subjectChoice.getSubjectName());
subjectDto.setScore(subjectChoice.getScore());
subjectDto.setAnalysis(subjectChoice.getAnalysis());
subjectDto.setLevel(subjectChoice.getLevel());
// 选择题类型匹配
SubjectTypeEnum subjectTypeEnum = SubjectTypeEnum.matchByValue(subjectChoice.getChoicesType());
if (subjectTypeEnum != null)
subjectDto.setType(subjectTypeEnum.getValue());
subjectDto.setChoicesType(subjectChoice.getChoicesType());
subjectDto.setOptions(subjectChoice.getOptions());
subjectDto.setCreator(subjectChoice.getCreator());
subjectDto.setModifier(subjectChoice.getModifier());
subjectDto.setModifyDate(subjectChoice.getModifyDate());
// 参考答案
Answer answer = new Answer();
answer.setAnswer(subjectChoice.getAnswer());
subjectDto.setAnswer(answer);
return subjectDto;
}
/**
* SubjectChoices转SubjectDto
*
* @param subjectChoices subjectChoices
* @return List
* @author tangyi
* @date 2019/06/16 16:50
*/
public static List<SubjectDto> subjectChoicesToDto(List<SubjectChoices> subjectChoices) {
List<SubjectDto> subjectDtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(subjectChoices)) {
subjectDtoList = subjectChoices.stream().map(SubjectUtil::subjectChoicesToDto).collect(Collectors.toList());
}
return subjectDtoList;
}
/**
* SubjectChoices转SubjectDto
*
* @param subjectChoices subjectChoices
* @return List
* @author tangyi
* @date 2019/06/16 16:50
*/
public static List<SubjectDto> subjectChoicesToDto(List<SubjectChoices> subjectChoices) {
List<SubjectDto> subjectDtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(subjectChoices)) {
subjectDtoList = subjectChoices.stream().map(SubjectUtil::subjectChoicesToDto).collect(Collectors.toList());
}
return subjectDtoList;
}
/**
* SubjectShortAnswer转SubjectDto
*
* @param subjectShortAnswer subjectShortAnswer
* @return List
* @author tangyi
* @date 2019/06/16 16:59
*/
public static SubjectDto subjectShortAnswerToDto(SubjectShortAnswer subjectShortAnswer) {
if (subjectShortAnswer == null)
return null;
SubjectDto subjectDto = new SubjectDto();
subjectDto.setId(subjectShortAnswer.getId());
subjectDto.setSubjectName(subjectShortAnswer.getSubjectName());
subjectDto.setScore(subjectShortAnswer.getScore());
subjectDto.setAnalysis(subjectShortAnswer.getAnalysis());
subjectDto.setLevel(subjectShortAnswer.getLevel());
subjectDto.setType(SubjectTypeEnum.SHORT_ANSWER.getValue());
subjectDto.setCreator(subjectShortAnswer.getCreator());
// 题目类型
subjectDto.setType(SubjectTypeEnum.SHORT_ANSWER.getValue());
/**
* SubjectShortAnswer转SubjectDto
*
* @param subjectShortAnswer subjectShortAnswer
* @return List
* @author tangyi
* @date 2019/06/16 16:59
*/
public static SubjectDto subjectShortAnswerToDto(SubjectShortAnswer subjectShortAnswer) {
if (subjectShortAnswer == null)
return null;
SubjectDto subjectDto = new SubjectDto();
subjectDto.setId(subjectShortAnswer.getId());
subjectDto.setSubjectName(subjectShortAnswer.getSubjectName());
subjectDto.setScore(subjectShortAnswer.getScore());
subjectDto.setAnalysis(subjectShortAnswer.getAnalysis());
subjectDto.setLevel(subjectShortAnswer.getLevel());
subjectDto.setType(SubjectTypeEnum.SHORT_ANSWER.getValue());
subjectDto.setCreator(subjectShortAnswer.getCreator());
subjectDto.setModifyDate(subjectShortAnswer.getModifyDate());
// 题目类型
subjectDto.setType(SubjectTypeEnum.SHORT_ANSWER.getValue());
// 参考答案
Answer answer = new Answer();
answer.setAnswer(subjectShortAnswer.getAnswer());
subjectDto.setAnswer(answer);
return subjectDto;
}
// 参考答案
Answer answer = new Answer();
answer.setAnswer(subjectShortAnswer.getAnswer());
subjectDto.setAnswer(answer);
return subjectDto;
}
/**
* SubjectShortAnswer转SubjectDto
*
* @param subjectShortAnswers subjectShortAnswers
* @return List
* @author tangyi
* @date 2019/06/16 16:59
*/
public static List<SubjectDto> subjectShortAnswerToDto(List<SubjectShortAnswer> subjectShortAnswers) {
List<SubjectDto> subjectDtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(subjectShortAnswers)) {
subjectDtoList = subjectShortAnswers.stream().map(SubjectUtil::subjectShortAnswerToDto)
.collect(Collectors.toList());
}
return subjectDtoList;
}
/**
* SubjectShortAnswer转SubjectDto
*
* @param subjectShortAnswers subjectShortAnswers
* @return List
* @author tangyi
* @date 2019/06/16 16:59
*/
public static List<SubjectDto> subjectShortAnswerToDto(List<SubjectShortAnswer> subjectShortAnswers) {
List<SubjectDto> subjectDtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(subjectShortAnswers)) {
subjectDtoList = subjectShortAnswers.stream().map(SubjectUtil::subjectShortAnswerToDto)
.collect(Collectors.toList());
}
return subjectDtoList;
}
}
......@@ -15,7 +15,7 @@ import lombok.Data;
*/
@Data
@ExcelModel("菜单信息")
@ContentRowHeight(10)
@ContentRowHeight(18)
@HeadRowHeight(20)
@ColumnWidth(25)
public class MenuExcelModel {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment