考试平台前端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

616 lines
20 KiB

<template>
<div class="page">
<Breadcrumb :data="crumbs" />
<p class="page-name mb">试卷</p>
<el-form :model="form" :rules="rules" class="input-form model" ref="form" label-width="140px">
<el-form-item prop="name" label="试卷名称">
<el-input style="width: 940px" placeholder="请输入试卷名称" v-model="form.name" clearable maxlength="100" />
</el-form-item>
<div class="item-line">
<el-form-item prop="particularYear" label="年份">
<el-date-picker v-model="form.particularYear" type="year" placeholder="请选择年份">
</el-date-picker>
</el-form-item>
<el-form-item required label="所属试卷分类">
<el-cascader placeholder="请选择所属试卷分类" :options="types" v-model="form.classificationId" :props="cascaderProps"
clearable style="width: 100%">
</el-cascader>
</el-form-item>
</div>
<div class="item-line">
<el-form-item prop="paperType" label="建议用途">
<el-select v-model="form.paperType" clearable placeholder="请选择建议用途">
<el-option v-for="(item, i) in paperTypes" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="suggestTime" label="估计用时">
<el-input placeholder="请输入估计用时" v-model.number="form.suggestTime">
<template slot="append">分钟</template>
</el-input>
</el-form-item>
</div>
<div class="item-line">
<el-form-item prop="professionals" label="所属专业">
<el-select v-model="form.professionalId" clearable placeholder="请选择所属专业">
<el-option v-for="(item, i) in professionals" :key="i" :label="item.professionalName"
:value="item.professionalId" filter></el-option>
</el-select>
</el-form-item>
<el-form-item prop="difficult" label="试卷难度">
<el-select v-model="form.difficult" clearable placeholder="请选择试卷难度">
<el-option v-for="(item, i) in difficults" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
</div>
<el-form-item prop="remarks" label="试卷说明">
<Ueditor ref="remarks" @ready="remarksReady" />
</el-form-item>
<div class="line"></div>
<div class="flex j-between m-b-20">
<p>默认模板</p>
<el-button type="primary" size="small" @click="showTemplate">选择大纲模板</el-button>
</div>
<el-table :data="form.paperOutline" stripe header-align="center" row-key="id">
<el-table-column type="index" width="60" label="序号" align="center"></el-table-column>
<el-table-column prop="outlineName" label="大题" align="center" min-width="120">
<template slot-scope="scope">第{{ arabicToChinese(scope.$index + 1) }}大题</template>
</el-table-column>
<el-table-column prop="name" label="题型" align="center" min-width="120">
<template slot-scope="scope">
<el-select v-model="scope.row.questionType" placeholder="请选择题型" @change="questionTypeChange(scope.row)">
<el-option v-for="(item, i) in questionTypes" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="name" label="目标题数" align="center" min-width="120">
<template slot-scope="scope">
<el-input placeholder="请输入目标题数" v-model.number="scope.row.questionNum" type="number" />
</template>
</el-table-column>
<el-table-column prop="name" label="目标分值" align="center" min-width="120">
<template slot-scope="scope">
<el-input placeholder="请输入目标分值" v-model="scope.row.targetScore" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="120">
<template slot-scope="scope">
<i class="el-icon-circle-plus-outline action-icon m-r-10" @click="addLine(scope.$index)"></i>
<i class="el-icon-remove-outline action-icon" @click="delLine(scope.$index)"></i>
</template>
</el-table-column>
</el-table>
<p class="m-t-10 m-b-20 text-right">目标总题数:{{ questionCount }};目标总分:{{ score }}</p>
<div class="line"></div>
<div class="flex j-between a-center">
<p>选择试题</p>
<div>
<el-button type="primary" @click="allocationAll">一键分配分值</el-button>
<el-button type="primary">自动选题</el-button>
</div>
</div>
<ul class="ques">
<li v-for="(item, i) in form.paperOutline" :key="i">
<div class="top">
<div>
<span class="serial">{{ arabicToChinese(i + 1) }}大题</span>
<el-input placeholder="请输入大题名称" v-model="item.outlineName" />
</div>
<div>
<el-button type="primary" @click="allocation(item)">一键分配分值</el-button>
<el-button type="primary" @click="showQuesDia(item)">批量添加</el-button>
<el-button type="danger" @click="batchDelQues(item)">批量删除试题</el-button>
{{ item.shrink }}
<i class="arrow el-icon-arrow-down" @click="item.shrink = !item.shrink"></i>
</div>
</div>
<p class="sum">本大题共{{ item.questionNum }}小题,共{{ item.targetScore }}分</p>
<div :class="['ques-wrap', { hide: item.shrink }]">
<div v-for="(ques, j) in item.examQuestions" :key="j" class="ques-item">
<el-checkbox v-model="ques.check"></el-checkbox>
<div class="ques-info">
<div class="top-line">
<span class="label">{{ j + 1 }} / 10</span>
<span class="label">{{ questionTypes.find(e => e.id === item.questionType).name }}</span>
<div v-html="ques.stem"></div>
<p>(<el-input class="score" placeholder="请输入" v-model="ques.score" />分)</p>
</div>
<!-- <div v-for="(opt, j) in ques.examQuestions" :key="j" class="opts">
<div v-html=""></div>
</div> -->
<div class="bottom-line">
<div class="correct">正确答案:A</div>
<div class="actions">
<div class="sort">
<span>排序</span>
<el-input class="score" v-model="ques.sort" />
</div>
<el-button type="text">编辑试题</el-button>
<el-button type="text" @click="showQuesDia(item, j, ques)">更换试题</el-button>
<el-button type="text" @click="showQuesDia(item, j)">添加试题</el-button>
<el-button type="text" @click="delQues(item, j)">移除试题</el-button>
</div>
</div>
</div>
</div>
</div>
</li>
</ul>
</el-form>
<div class="btns">
<el-button @click="submit(1)">保存草稿</el-button>
<el-button type="primary" @click="submit(0)">发布</el-button>
<el-button v-if="$route.query.id" @click="preview">预览</el-button>
<el-button @click="back">取消</el-button>
</div>
<Template :visible.sync="templateVisible" />
<Manual :visible.sync="quesVisible" :questionType.sync="curType.questionType" />
</div>
</template>
<script>
import Setting from '@/setting'
import Util from '@/libs/util'
import dayjs from 'dayjs'
import Ueditor from '@/components/ueditor'
import Breadcrumb from '@/components/breadcrumb'
import Template from './template'
import Manual from './manual'
import QuesConst from '@/const/ques'
import TestPaperConst from '@/const/testPaper'
export default {
components: { Ueditor, Breadcrumb, Template, Manual },
data () {
return {
crumbs: [
{
name: '试卷管理',
route: 'list'
},
{
name: '创建试卷'
},
],
questionTypes: QuesConst.questionTypes,
difficults: TestPaperConst.difficults,
paperTypes: TestPaperConst.paperTypes,
arabicToChinese: Util.arabicToChinese,
libraryId: this.$route.query.libraryId,
headers: {
token: Util.local.get(Setting.tokenKey)
},
types: [],
cascaderProps: {
checkStrictly: true,
label: "classificationName",
value: "classificationId"
},
professionals: [],
form: {
classificationId: +this.$route.query.classificationId || '',
difficult: '',
libraryId: '',
name: '',
paperMethod: '',
paperType: '',
particularYear: '',
professionalId: '',
remarks: '',
score: '',
questionCount: 0,
suggestTime: '',
paperOutline: [
{
examQuestions: [],
outlineName: '单选题',
questionNum: '10',
questionType: 'single_choice',
targetScore: '20.0',
shrink: false,
},
{
examQuestions: [],
outlineName: '多选题',
questionNum: '5',
questionType: 'multiple_choice',
targetScore: '15.0',
shrink: false,
},
{
examQuestions: [],
outlineName: '判断题',
questionNum: '5',
questionType: 'judgement',
targetScore: '10.0',
shrink: false,
},
{
examQuestions: [],
outlineName: '填空题',
questionNum: '5',
questionType: 'fill_blank',
targetScore: '15.0',
shrink: false,
},
{
examQuestions: [],
outlineName: '问答题',
questionNum: '3',
questionType: 'essay',
targetScore: '40.0',
shrink: false,
},
],
},
rules: {
name: [
{ required: true, message: '请输入试卷名称', trigger: 'blur' }
],
particularYear: [
{ required: true, message: '请选择年份', trigger: 'change' }
],
paperType: [
{ required: true, message: '请选择建议用途', trigger: 'change' }
],
suggestTime: [
{ required: true, message: '请输入估计用时', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入试卷名称', trigger: 'blur' }
],
},
templateVisible: false,
quesVisible: false,
curType: {},
curQuesIndex: 0,
curQues: null,
};
},
computed: {
// 目标总题数
questionCount () {
return this.form.paperOutline.reduce((e, j) => (e += +j.questionNum), 0)
},
// 目标总分
score () {
return this.form.paperOutline.reduce((e, j) => (e += +j.targetScore), 0)
}
},
mounted () {
this.getType()
this.getProfessional()
},
methods: {
// 获取试卷分类
async getType () {
try {
const res = await this.$post(this.api.examClassificationList, {
libraryId: this.libraryId,
})
const data = res.treeList
this.handleType(data)
this.types = data
} catch (e) { }
},
// 获取所属专业
async getProfessional () {
try {
const res = await this.$post(this.api.queryProfessional, {
pageNum: 1,
pageSize: 1000,
})
this.professionals = res.pageList.records
} catch (e) { }
},
// 处理树形
handleType (list) {
list.map(e => {
if (e.children && e.children.length) {
this.handleType(e.children)
} else {
delete e.children
}
})
},
// 试卷说明富文本加载完毕回调
remarksReady (editor) {
this.form.remarks && editor.setContent(this.form.remarks)
},
// 选择大纲模板
showTemplate () {
this.templateVisible = true
},
// 大纲题型选择回调
questionTypeChange (row) {
// 如果大题名称还是默认的名字(即5个题型的名字),那么修改了题型后需要同步修改大题名称,用户自定义了大题名称,则不修改
if (this.questionTypes.find(e => e.name === row.outlineName)) row.outlineName = this.questionTypes.find(e => e.id === row.questionType).name
},
// 试卷大纲添加行
addLine (i) {
this.form.paperOutline.splice(i + 1, 0, {
examQuestions: [],
outlineName: '',
questionNum: '',
questionType: '',
targetScore: '',
shrink: false,
})
},
// 试卷大纲移除行
delLine (i) {
this.form.paperOutline.length > 1 && this.form.paperOutline.splice(i, 1)
},
// 一键分配所有题目的分值
async allocationAll () {
const paper = this.form.paperOutline
const { data } = await this.$post(this.api.avgValues, {
avgValueList: paper.map(e => {
return {
questionNum: e.examQuestions.length,
targetScore: e.targetScore,
}
})
})
if (data && data.avgValueList && data.avgValueList.length) {
data.avgValueList.map((e, i) => {
// scores里有-1则不用循环
e.scores.includes(-1) || e.scores.map((n, j) => {
paper[i].examQuestions[j].score = n
})
})
}
},
// 添加题目
showQuesDia (item, i, ques) {
this.curType = item
this.curQuesIndex = i
this.curQues = ques
this.quesVisible = true
},
// 批量删除试题
async batchDelQues (item) {
try {
const checked = item.examQuestions.filter(e => e.check)
if (checked.length) {
await this.$confirm(`确认要删除吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
closeOnClickModal: false,
})
checked.map(e => {
const cur = item.examQuestions.findIndex(n => n.questionVersionId === e.questionVersionId)
cur >= 0 && item.examQuestions.splice(cur, 1)
})
} else {
Util.warningMsg('请选择数据')
}
} catch (e) { }
},
// 大题一键分配分值
allocation (item) {
const total = +item.targetScore
const len = item.examQuestions.length
const quotient = Math.floor(total / len) // 平均分配的整数部分
let remainder = total % len // 余数
// 分配平均分
item.examQuestions.forEach(e => {
e.score = quotient
})
// 处理余数
while (remainder > 0) {
for (let i = 0; i < len && remainder > 0; i++) {
item.examQuestions[i].score += 1
remainder -= 1
}
}
},
// 移除试题
delQues (item, i) {
item.examQuestions.splice(i, 1)
},
// 提交
async submit (status) {
// this.form.paperOutline[0].examQuestions.reduce((prev, curr) => {
// console.log(111, prev, curr)
// // + prev.score + +curr.score
// }, 0)
// return console.log(44,)
if (this.submiting) return false
this.$refs.form.validate(async (valid) => {
if (valid) {
const form = _.cloneDeep(this.form)
if (isNaN(form.suggestTime) || form.suggestTime < 1 || form.suggestTime % 1) return Util.warningMsg('估计用时请输入正整数')
const paper = form.paperOutline
let invalid = 0
for (const i in paper) {
const e = paper[i]
if (!e.questionType) {
Util.warningMsg('请选择题型')
invalid = 1
break
}
if (!e.questionNum) {
Util.warningMsg('请输入目标题数')
invalid = 1
break
}
if (!e.targetScore) {
Util.warningMsg('请输入目标分值')
invalid = 1
break
}
// 该大题里的小题总数需等于目标题数
if (+e.questionNum !== e.examQuestions.length) {
Util.warningMsg(`${this.arabicToChinese(i + 1)}大题的小题总数跟目标题数不一致,请重新修改`)
invalid = 1
break
}
// 该大题里的小题总分需等于目标分值
// if (+e.targetScore !== e.examQuestions.length) {
// Util.warningMsg(`第${this.arabicToChinese(i + 1)}大题的小题总分跟目标分值不一致,请重新修改`)
// invalid = 1
// break
// }
}
if (invalid) return false
this.submiting = true
form.particularYear = +(dayjs(form.particularYear).format('YYYY'))
form.libraryId = this.libraryId
form.questionCount = this.questionCount
form.remarks = this.$refs.remarks.getUEContent()
form.score = this.score
form.status = status
paper.map(e => {
e.targetScore = +e.targetScore
e.examQuestions = e.examQuestions.map(n => {
return {
questionVersionId: n.questionVersionId,
score: +n.score,
}
})
})
debugger
if (typeof form.classificationId === 'object') form.classificationId = form.classificationId[form.classificationId.length - 1]
form.questionType = [...new Set(paper.map(e => e.questionType))].join('、')
form.createSource = 1
try {
await this.$post(this.api.saveExamPaper, form)
Util.successMsg('保存成功')
this.submiting = false
this.back()
} catch (e) {
this.submiting = false
}
}
})
},
// 返回
back () {
this.$router.back()
}
}
};
</script>
<style lang="scss" scoped>
.ques {
li {
padding: 15px;
margin: 20px 0 10px;
border: 1px solid #dbdbdb;
}
.top {
display: flex;
justify-content: space-between;
align-items: center;
}
.serial {
margin-right: 10px;
font-size: 13px;
}
.arrow {
margin-left: 10px;
font-size: 16px;
color: #a9a9a9;
cursor: pointer;
}
.sum {
margin: 15px 0;
font-size: 13px;
color: $main-color;
}
.ques-wrap {
// transition: 2s;
transition-property: height;
transition-duration: .25s, 1s;
&.hide {
height: 0;
}
}
.ques-item {
display: flex;
align-items: flex-start;
margin-bottom: 20px;
}
.ques-info {
width: calc(100% - 40px);
padding: 10px;
margin-left: 20px;
border: 1px solid #e9e9e9;
}
.top-line {
display: flex;
align-items: center;
}
.label {
padding: 3px;
margin-right: 10px;
font-size: 12px;
line-height: 1;
color: $main-color;
border: 1px solid;
border-radius: 2px;
}
/deep/.score {
width: 70px;
margin: 0 10px;
.el-input__inner {
height: 28px;
line-height: 28px;
}
}
.bottom-line {
display: flex;
justify-content: space-between;
align-items: center;
}
.correct {
font-size: 13px;
}
.actions {
display: inline-flex;
align-items: center;
.el-button {
margin-left: 20px;
}
}
.sort {
display: inline-flex;
align-items: center;
font-size: 12px;
}
}
</style>