试题联调

master
yujialong 4 months ago
parent 2ae0f95eaf
commit d2a9e6721c
  1. 5
      package-lock.json
  2. 1
      package.json
  3. 9
      src/api/index.js
  4. 1
      src/components/ueditor/index.vue
  5. 3
      src/const/column.js
  6. 42
      src/const/ques.js
  7. 18
      src/libs/util.js
  8. 551
      src/pages/ques/detail/index.vue
  9. 694
      src/pages/ques/index.vue
  10. 560
      src/pages/ques/list/index.vue
  11. 6
      src/pages/quesBank/index.vue
  12. 2
      src/pages/quesBankType/index.vue
  13. 15
      src/pages/testPaper/detail/index.vue
  14. 198
      src/pages/testPaper/detail/manual.vue
  15. 3
      src/pages/testPaper/list/index.vue
  16. 2
      src/pages/testPaperLibrary/index.vue
  17. 2
      src/pages/testPaperLibraryType/index.vue
  18. 7
      src/router/modules/ques.js

5
package-lock.json generated

@ -4104,6 +4104,11 @@
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz",
"integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI="
}, },
"dayjs": {
"version": "1.11.12",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz",
"integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg=="
},
"de-indent": { "de-indent": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",

@ -11,6 +11,7 @@
"@tinymce/tinymce-vue": "^3.2.8", "@tinymce/tinymce-vue": "^3.2.8",
"axios": "^0.18.0", "axios": "^0.18.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"dayjs": "^1.11.12",
"echarts": "^4.8.0", "echarts": "^4.8.0",
"element-theme": "^2.0.1", "element-theme": "^2.0.1",
"element-ui": "^2.13.0", "element-ui": "^2.13.0",

@ -1,5 +1,5 @@
import Setting from '@/setting' import Setting from '@/setting'
const host = 'http://192.168.31.51:9000' const { apiBaseURL: host } = Setting
export default { export default {
queryProfessional: `/exam/exam/professional/queryProfessional`, queryProfessional: `/exam/exam/professional/queryProfessional`,
@ -52,4 +52,11 @@ export default {
examPaperTemplateList: `/exam/exam/paperTemplate/examPaperTemplateList`, examPaperTemplateList: `/exam/exam/paperTemplate/examPaperTemplateList`,
saveExamPaperTemplate: `/exam/exam/paperTemplate/saveExamPaperTemplate`, saveExamPaperTemplate: `/exam/exam/paperTemplate/saveExamPaperTemplate`,
templateDetails: `/exam/exam/paperTemplate/templateDetails`, templateDetails: `/exam/exam/paperTemplate/templateDetails`,
addQuestion: `/exam/questions/addQuestion`,
findQuestion: `/exam/questions/findById`,
listQuestion: `/exam/questions/pagingQuery`,
updateQuestion: `/exam/questions/updateQuestion`,
batchImportQuestions: `${host}/exam/questions/batchImportQuestions`,
checkQuestion: `/exam/questions/checkQuestion`,
} }

@ -25,6 +25,7 @@ export default {
}, },
watch: { watch: {
value: function (val, oldVal) { value: function (val, oldVal) {
console.log("🚀 ~ val:", val, this.ready)
if (val != null && this.ready) { if (val != null && this.ready) {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
this.instance = UE.getEditor(this.randomId) this.instance = UE.getEditor(this.randomId)

@ -1,3 +0,0 @@
export default {
}

@ -0,0 +1,42 @@
export default {
difficults: [
{
id: 'basic',
name: '基础'
},
{
id: 'easy',
name: '普通'
},
{
id: 'medium',
name: '较难'
},
{
id: 'hard',
name: '难'
},
],
questionTypes: [
{
id: 'single_choice',
name: '单选题'
},
{
id: 'multiple_choice',
name: '多选题'
},
{
id: 'judgement',
name: '判断题'
},
{
id: 'fill_blank',
name: '填空题'
},
{
id: 'essay',
name: '问答题'
},
],
}

@ -133,7 +133,25 @@ const util = {
k++; k++;
} }
return num > 9 && num < 20 ? re.slice(1) : re; return num > 9 && num < 20 ? re.slice(1) : re;
},
// 阿拉伯数字转化为英文字母
numToLetter (num) {
let result = ''
if (num > 26) {
result += numberToLetter((num / 26) >> 0 - 1)
} }
result += String.fromCharCode(65 + (num % 26))
return result
},
// 去掉html里的标签及空格
removeTag (list, prop = 'stem') {
list.map(e => {
const el = document.createElement('div')
el.innerHTML = e[prop]
e[prop] = el.innerText
})
return list
},
}; };
export default util; export default util;

@ -0,0 +1,551 @@
<template>
<div>
<el-dialog :title="readonly ? '查看试题' : !form.questionId ? '新增试题' : '编辑试题'" :visible.sync="quesVisible"
width="1000px" :close-on-click-modal="false" @closed="closeDia">
<el-form :model="form" :rules="rules" ref="form" label-width="110px" :disabled="readonly">
<el-form-item label="题库分类">
<el-input v-model="questionBankCategory" disabled />
</el-form-item>
<el-form-item label="题库">
<el-input v-model="questionBankName" disabled />
</el-form-item>
<el-form-item prop="specialtyIds" label="所属专业">
<el-select v-model="form.specialtyIds" clearable multiple 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="knowledgePointIds" label="所属知识点">
<el-cascader placeholder="请选择所属知识点" v-model="form.knowledgePointIds" :options="knowledges"
:props="{ value: 'id', label: 'name', multiple: true, checkStrictly: true }" clearable></el-cascader>
</el-form-item>
<el-form-item prop="givenYear" label="年份">
<el-date-picker v-model="form.givenYear" type="year" placeholder="请选择年份" format="yyyy" value-format="yyyy">
</el-date-picker>
</el-form-item>
<el-form-item prop="difficulty" label="难度">
<el-select v-model="form.difficulty" 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>
<el-form-item prop="questionType" label="题型">
<el-radio-group v-model="form.questionType" @change="questionTypeChange">
<el-radio v-for="(item, i) in questionTypes" :key="i" :label="item.id">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item class="is-required" prop="stem" label="题干">
<Ueditor ref="stem" @ready="stemReady" v-model="form.stem" />
</el-form-item>
<el-form-item v-if="form.questionType !== 'fill_blank' && form.questionType !== 'essay'" label="正确答案" required>
<div class="opts">
<div v-for="(item, i) in form.questionAnswerVersions" :key="i" class="line">
<el-checkbox v-if="form.questionType === 'multiple_choice'" class="correct-check"
v-model="item.answerIsCorrect" :true-label="1">选项{{
numToLetter(i)
}}</el-checkbox>
<el-radio v-else v-model="item.answerIsCorrect" :true-label="1" :label="1" @change="correctChange(i)">选项{{
numToLetter(i)
}}</el-radio>
<el-input placeholder="请输入" v-model="item.optionText"></el-input>
<template v-if="form.questionType !== 'judgement'">
<i class="icon el-icon-circle-plus-outline" @click="addOpt(i)"></i>
<i class="icon el-icon-remove-outline" @click="delOpt(i)"></i>
</template>
</div>
</div>
</el-form-item>
<!-- 填空题特有 -->
<template v-if="form.questionType === 'fill_blank'">
<el-form-item label="正确答案" required>
<div class="opts fill-blanks">
<div v-for="(item, i) in fillBlanks" :key="i" class="line j-between">
<div class="fills">
<span>填空{{ i + 1 }}</span>
<div v-for="(fill, j) in item.fills" :key="i" class="fill-item">
<el-input placeholder="请输入" v-model="fill.val" />
<i v-if="j" class="action-icon el-icon-remove-outline" @click="item.fills.splice(j, 1)"></i>
<span v-if="j !== item.fills.length - 1"></span>
</div>
<el-button class="add-fill" type="primary" size="small" @click="addFill(item)">新增答案</el-button>
</div>
<div class="score-wrap">
<span>分值占比</span>
<el-input class="score" placeholder="请输入" size="small" v-model="item.scoreProportion" />
<span>%</span>
</div>
</div>
</div>
</el-form-item>
<el-form-item prop="gradingStandard" label="判分标准" required>
<el-select :value="form.questionAnswerVersions[0].gradingStandard" placeholder="请选择判分标准" disabled>
<el-option label="完全一致" value="exact_match"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="referenceAnswer" label="判分标准">
<div>
<el-switch disabled active-text="判分时忽略答案中的字母大小写"></el-switch>
</div>
<div>
<el-switch disabled active-text="判分时忽略答案中的空格"></el-switch>
</div>
<div>
<el-switch disabled active-text="允许学生每个填空的答案与正确答案的顺序不一致"></el-switch>
</div>
<div>
<el-switch disabled active-text="判分时允许数值相等"></el-switch>
</div>
</el-form-item>
</template>
<!-- 问答题特有 -->
<template v-if="form.questionType === 'essay'">
<el-form-item prop="userName" label="上传题干文件">
<el-input placeholder="请输入题库名称" v-model="form.name"></el-input>
</el-form-item>
<el-form-item prop="allowAttachment" label="支持学生上传附件" label-width="130px">
<el-radio-group v-model="form.allowAttachment">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="gradingStandard" label="判分标准">
<el-select :value="form.questionAnswerVersions[0].gradingStandard" placeholder="请选择判分标准" disabled>
<el-option label="人工判分" value="manual"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="referenceAnswer" label="参考答案">
<Ueditor ref="referenceAnswer" @ready="referenceAnswerReady"
v-model="form.questionAnswerVersions[0].referenceAnswer" />
</el-form-item>
</template>
<el-form-item prop="answerAnalysis" label="解析">
<Ueditor ref="answerAnalysis" @ready="answerAnalysisReady" v-model="answerAnalysis" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" :loading="submiting && keep === 0" @click="submit(0)">保存</el-button>
<el-button type="primary" :loading="submiting && keep === 1" @click="submit(1)">保存并继续新增</el-button>
<el-button @click="quesVisible = false">取消</el-button>
</span>
</el-dialog>
<el-dialog title="提示" :visible.sync="repeatVisible" width="800px" :close-on-click-modal="false">
<el-alert title="与当前题库中已有的试题重复(如下),是否继续保存?" type="warning" effect="dark" :closable="false">
</el-alert>
<el-table class="m-t-10" :data="repeats" stripe header-align="center" row-key="id">
<el-table-column type="index" width="60" label="序号" align="center"></el-table-column>
<el-table-column prop="name" label="题型" align="center" min-width="120">
<template slot-scope="scope">{{ questionTypes.find(e => e.id === scope.row.questionType) ?
questionTypes.find(e => e.id === scope.row.questionType).name : '' }}</template>
</el-table-column>
<el-table-column prop="stem" label="题干" align="center" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" align="center" width="240">
<template slot-scope="scope">
<el-button type="text">详情</el-button>
</template>
</el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="repeatVisible = false">取消</el-button>
<el-button type="primary" @click="saveQues">确定</el-button>
</span>
</el-dialog>
<el-dialog :visible.sync="richEditor.dialogVisible" width="1000px" :show-close="false" center
:close-on-click-modal="false">
<!-- <Ueditor ref="title2" @ready="editorReady" /> -->
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="editorSubmit">确定</el-button>
<el-button @click="richEditor.dialogVisible = false">取消</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import Setting from '@/setting'
import Util from '@/libs/util'
import Ueditor from '@/components/ueditor'
import dayjs from 'dayjs'
import _ from 'lodash'
import Const from '@/const/ques'
export default {
props: ['visible', 'row', 'readonly'],
components: { Ueditor },
data () {
return {
typeId: this.$route.query.id,
questionBankName: this.$route.query.questionBankName,
questionBankCategory: this.$route.query.questionBankCategory,
numToLetter: Util.numToLetter,
radio: '',
types: [],
cascaderValue: [],
cascaderProps: {
checkStrictly: true,
label: "classificationName",
value: "classificationId"
},
professionals: [],
difficults: Const.difficults,
questionTypes: Const.questionTypes,
knowledges: [],
richEditor: {
object: null,
parameterName: '',
instance: null
},
originForm: {},
originOpt: {
answerAnalysis: '',
answerIsCorrect: '',
optionNumber: '',
optionText: '',
},
answerAnalysis: '',
fillBlanks: [
{
serial: 1,
fills: [{
val: ''
}],
scoreProportion: ''
}
],
form: {
difficulty: '',
givenYear: new Date(),
knowledgePointIds: [],
questionAnswerVersions: [],
questionBankId: '',
questionType: 'single_choice',
specialtyIds: [1],
stem: '',
allowAttachment: 0,
stemAttachment: '',
uploadInstructions: '',
},
rules: {
knowledgePointIds: [
{ required: true, message: '请选择知识点', trigger: 'change' }
],
difficulty: [
{ required: true, message: '请选择难度', trigger: 'change' }
],
questionType: [
{ required: true, message: '请选择题型', trigger: 'change' }
],
},
templateVisible: false,
quesVisible: false,
submiting: false,
tempForm: {},
keep: 0,
repeatVisible: false,
repeats: []
};
},
watch: {
visible () {
this.quesVisible = this.visible
this.visible && this.init()
}
},
mounted () {
this.originForm = _.cloneDeep(this.form)
},
methods: {
init () {
this.form = _.cloneDeep(this.originForm)
//
for (let i = 1; i < 5; i++) {
const opt = _.cloneDeep(this.originOpt)
opt.optionNumber = i
this.form.questionAnswerVersions.push(opt)
}
this.form.questionAnswerVersions[0].answerIsCorrect = 1
this.$refs.stem.setText('')
this.$refs.answerAnalysis.setText('')
this.getDetail()
this.getKnowledge()
this.getProfessional()
},
//
async getDetail () {
try {
const { row } = this
if (row.questionId) {
const res = await this.$post(`${this.api.findQuestion}?questionId=${this.row.questionId}&version=${this.row.version}`)
const r = res.message
const opts = r.questionAnswerVersionsList
this.form = {
questionId: r.questionId,
questionType: r.questionType,
questionVersionId: r.questionVersionId,
version: r.version,
givenYear: r.givenYear,
difficulty: r.difficulty,
stem: r.stem,
knowledgePointIds: r.knowledgePointList.map(e => {
return e.path.split('/').map(n => +n)
}),
specialtyIds: r.professionalList.map(e => e.specialtyId),
questionAnswerVersions: opts,
}
this.answerAnalysis = opts[0].answerAnalysis
}
} catch (e) { }
},
//
async getKnowledge () {
try {
const { data } = await this.$post(this.api.TreeStructure, {
createSource: 1,
questionBankId: this.typeId,
keyword: ''
})
this.handleType(data)
this.knowledges = 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.type) e.disabled = true
if (e.children && e.children.length) {
this.handleType(e.children)
} else {
delete e.children
}
})
},
//
questionTypeChange (val) {
//
if (val === 'judgement') {
const opts = []
for (let i = 1; i < 3; i++) {
const opt = _.cloneDeep(this.originOpt)
opt.optionNumber = i
opts.push(opt)
}
opts[0].answerIsCorrect = 1
opts[0].optionText = '正确'
opts[1].optionText = '错误'
this.form.questionAnswerVersions = opts
} else if (val === 'essay') {
//
const opt = _.cloneDeep(this.originOpt)
opt.gradingStandard = 'manual'
opt.referenceAnswer = ''
this.form.questionAnswerVersions = [opt]
}
},
//
stemReady (editor) {
this.form.stem && editor.setContent(this.form.stem)
},
//
answerAnalysisReady (editor) {
this.form.answerAnalysis && editor.setContent(this.answerAnalysis)
},
//
correctChange (i) {
this.form.questionAnswerVersions.map(e => {
e.answerIsCorrect = 0
})
this.form.questionAnswerVersions[i].answerIsCorrect = 1
},
//
addOpt (i) {
const opt = _.cloneDeep(this.originOpt)
opt.optionNumber = i + 2
this.form.questionAnswerVersions.splice(i + 1, 0, opt)
},
//
delOpt (i) {
this.form.questionAnswerVersions.length > 2 && this.form.questionAnswerVersions.splice(i, 1)
},
editorReady (instance) {
console.log("🚀 ~ editorReady ~ instance:", instance)
// this.richEditor.instance = instance
// let currentContent = this.richEditor.object[this.richEditor.parameterName]
// this.richEditor.instance.setContent(currentContent)
// // Ueditor
// this.richEditor.instance.focus(true)
},
inputClick (object, parameterName) {
this.richEditor.object = object
this.richEditor.parameterName = parameterName
this.richEditor.dialogVisible = true
},
editorSubmit () {
let content = this.richEditor.instance.getContent()
if (this.richEditor.parameterName === 'title') { //
// if (this.questionItemReset(content)) {
// this.richEditor.object[this.richEditor.parameterName] = content
// this.richEditor.dialogVisible = false
// }
} else {
this.richEditor.object[this.richEditor.parameterName] = content
this.richEditor.dialogVisible = false
}
},
questionItemReset (content) {
let spanRegex = new RegExp('<span class="gapfilling-span (.*?)">(.*?)<\\/span>', 'g')
let _this = this
let newFormItem = []
let gapfillingItems = content.match(spanRegex)
if (gapfillingItems === null) {
this.$message.error('请插入填空')
return false
}
gapfillingItems.forEach(function (span, index) {
let pairRegex = /<span class="gapfilling-span (.*?)">(.*?)<\/span>/
pairRegex.test(span)
newFormItem.push({ id: null, itemUuid: RegExp.$1, prefix: RegExp.$2, content: '', score: '0' })
})
newFormItem.forEach(function (item) {
_this.form.items.some((oldItem, index) => {
if (oldItem.itemUuid === item.itemUuid) {
item.content = oldItem.content
item.id = oldItem.id
item.score = oldItem.score
return true
}
})
})
_this.form.items = newFormItem
return true
},
//
addFill (item) {
item.fills.push({ val: '' })
},
//
submit (keep) {
if (this.submiting) return false
this.$refs.form.validate(async (valid) => {
if (valid) {
const form = _.cloneDeep(this.form)
const stem = this.$refs.stem.getUEContent()
const answerAnalysis = this.$refs.answerAnalysis.getUEContent()
const opt = form.questionAnswerVersions
if (!stem) return Util.warningMsg('请输入题干')
let invalid = 0
for (const e of opt) {
if (!e.optionText) {
Util.warningMsg('选项请输入完整')
invalid = 1
break
}
}
// debugger
if (invalid) return false
if (!opt.find(e => e.answerIsCorrect)) return Util.warningMsg('请设置正确答案')
this.submiting = true
try {
form.stem = stem
form.questionBankId = this.typeId
form.knowledgePointIds = form.knowledgePointIds.map(e => {
return e[e.length - 1]
})
form.givenYear = dayjs(form.givenYear).format('YYYY')
if (answerAnalysis) form.questionAnswerVersions[0].answerAnalysis = answerAnalysis //
//
this.tempForm = form
this.keep = keep
const res = await this.$post(this.api.checkQuestion, form)
//
if (res.status === 200) {
this.saveQues()
}
} catch (e) {
//
setTimeout(() => {
this.repeatVisible = true
this.repeats = Util.removeTag(e)
this.submiting = false
}, 1500)
}
}
})
},
//
async saveQues () {
const form = this.tempForm
await this.$post(this.api[form.id ? 'updateQuestion' : 'addQuestion'], form)
Util.successMsg('保存成功')
this.submiting = false
this.keep ? this.init() : (this.quesVisible = false)
this.repeatVisible = false
},
//
closeDia () {
this.$emit('update:visible', false)
}
}
};
</script>
<style lang="scss" scoped>
.action-icon {
margin-right: 10px;
font-size: 18px;
color: $main-color;
cursor: pointer;
}
.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;
}
.sum {
margin: 15px 0;
font-size: 13px;
color: $main-color;
}
}
/deep/.correct-check {
margin-right: 15px;
}
</style>

@ -1,694 +0,0 @@
<template>
<div>
<Breadcrumb :data="crumbs" />
<div class="page">
<div class="side">
<div class="m-b-20">
<el-radio-group v-model="type" @change="typeChange">
<div class="m-b-20">
<el-radio :label="1">所有试题</el-radio>
</div>
<div>
<el-radio :label="2">未分配知识点的试题</el-radio>
</div>
</el-radio-group>
</div>
<el-divider></el-divider>
<div>
<div class="flex-between m-b-10">
<h6 class="page-name" style="margin-bottom: 0">知识点框架</h6>
<el-button type="text" @click="toSet">设置</el-button>
</div>
<el-input class="m-b-10" placeholder="请输入知识点分类、知识点名称" prefix-icon="el-icon-search" size="small"
v-model="keyword" clearable></el-input>
<div style="height: 504px; max-height: 504px; overflow: auto">
<el-tree :data="types" default-expand-all ref="orgTree" node-key="id" highlight-current
:expand-on-click-node="false" @node-click="handleNodeClick" :props="{ label: 'name', isLeaf: 'leaf' }">
<span class="custom-tree-node" slot-scope="{ node, data }">
<img v-if="data.type" class="m-r-5" src="@/assets/images/knowledge.svg" alt="">
<span class="org-name">{{ data.name }}</span>
</span>
</el-tree>
</div>
</div>
</div>
<div class="right">
<h6 class="page-name">筛选</h6>
<div class="tool">
<ul class="filter">
<li>
<label>题目类型</label>
<el-select v-model="filter.status" clearable placeholder="请选择题目类型" @change="getList">
<el-option v-for="(item, i) in status" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</li>
<li>
<label>专业</label>
<el-select v-model="filter.status" clearable placeholder="请选择专业" @change="getList">
<el-option v-for="(item, i) in status" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</li>
<li>
<label>试题难度</label>
<el-select v-model="filter.status" clearable placeholder="请选择试题难度" @change="getList">
<el-option v-for="(item, i) in status" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</li>
<li>
<label>知识点</label>
<el-select v-model="filter.status" clearable placeholder="请选择知识点" @change="getList">
<el-option v-for="(item, i) in status" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</li>
<li>
<label>正确率</label>
<el-input style="width: 80px;" placeholder="请输入" v-model="filter.keyWord" clearable />
<span style="margin: 0 10px;">% -- </span>
<el-input style="width: 80px;" placeholder="请输入" v-model="filter.keyWord" clearable />
<span style="margin-left: 10px;">%</span>
</li>
<li>
<label>状态</label>
<el-select v-model="filter.status" clearable placeholder="请选择状态" @change="getList">
<el-option v-for="(item, i) in status" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</li>
<li>
<label>搜索</label>
<el-input style="width: 250px;" placeholder="请输入题干" prefix-icon="el-icon-search" v-model="filter.keyWord"
clearable />
</li>
<div style="margin-bottom: 15px;">
<el-button type="primary" @click="add">新增试题</el-button>
<el-button type="primary" @click="batchImport">批量导入</el-button>
<!-- <el-button type="primary" @click="add">批量转移</el-button> -->
<el-button type="primary" @click="add">批量移除 </el-button>
<el-button type="primary" @click="add">批量删除</el-button>
</div>
</ul>
</div>
<el-table :data="list" class="table" ref="table" stripe header-align="center"
@selection-change="handleSelectionChange" row-key="id">
<el-table-column type="selection" width="55" align="center" :reserve-selection="true"></el-table-column>
<el-table-column type="index" width="60" label="序号" align="center"></el-table-column>
<el-table-column prop="userName" label="题干" align="center" min-width="100"></el-table-column>
<el-table-column prop="account" label="题型" align="center" min-width="100"></el-table-column>
<el-table-column prop="phone" label="专业" align="center" min-width="120"></el-table-column>
<el-table-column prop="invitationAccount" label="知识点" align="center" min-width="120"></el-table-column>
<el-table-column prop="invitationAccount" label="年份" align="center" min-width="120"></el-table-column>
<el-table-column prop="invitationAccount" label="难度" align="center" min-width="120"></el-table-column>
<el-table-column prop="invitationAccount" label="正确率" align="center" min-width="120"></el-table-column>
<el-table-column prop="loginNumber" label="更新时间" align="center" width="120"></el-table-column>
<el-table-column prop="lastLoginTime" label="最近编辑人" align="center" width="120"></el-table-column>
<el-table-column prop="lastLoginTime" label="已引用试卷(套)" align="center" width="120"></el-table-column>
<el-table-column label="操作" align="center" width="300">
<template slot-scope="scope">
<el-button type="text" @click="edit(scope.row)">复制</el-button>
<el-button type="text" @click="edit(scope.row)">查看</el-button>
<el-button type="text" @click="edit(scope.row)">编辑</el-button>
<el-button type="text" @click="del(scope.row)">移除</el-button>
<el-button type="text" @click="del(scope.row)">删除</el-button>
<el-switch v-model="scope.row.ztOpen" :active-value="0" :inactive-value="1" style="margin: 0 10px 0 5px"
:active-text="scope.row.ztOpen ? '关' : '开'"
@change="switchOff($event, scope.row, scope.$index)"></el-switch>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination background @current-change="currentChange" :current-page="page"
layout="total, prev, pager, next" :total="total"></el-pagination>
</div>
</div>
</div>
<el-dialog :title="!form.id ? '创建题目' : '编辑题目'" :visible.sync="quesVisible" width="1000px"
:close-on-click-modal="false">
<el-form label-width="110px">
<el-form-item prop="userName" label="题库分类">
<el-select style="width: 100%" v-model="form.provinceId" placeholder="请选择题库分类">
<el-option v-for="(item, i) in types" :key="i" :label="item.provinceName"
:value="item.provinceId"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="userName" label="题库">
<el-input placeholder="请输入题库名称" v-model="form.name" disabled></el-input>
</el-form-item>
<el-form-item prop="userName" label="所属专业">
<el-input placeholder="请输入题库名称" v-model="form.name"></el-input>
</el-form-item>
<el-form-item prop="userName" label="所属知识点">
<el-input placeholder="请输入题库名称" v-model="form.name"></el-input>
</el-form-item>
<el-form-item prop="userName" label="年份">
<el-input placeholder="请输入题库名称" v-model="form.name"></el-input>
</el-form-item>
<el-form-item prop="userName" label="难度">
<el-input placeholder="请输入题库名称" v-model="form.name"></el-input>
</el-form-item>
<el-form-item prop="userName" label="题型">
<el-input placeholder="请输入题库名称" v-model="form.name"></el-input>
</el-form-item>
<el-form-item prop="userName" label="题干">
<Ueditor ref="title" />
</el-form-item>
<el-form-item label="正确答案" required>
<div class="opts">
<div class="line">
<el-radio v-model="radio" label="1">选项A</el-radio>
<el-input placeholder="请输入" v-model="form.name"></el-input>
<i class="icon el-icon-circle-plus-outline" @click="addOpt"></i>
<i class="icon el-icon-remove-outline" @click="delOpt"></i>
</div>
<div class="line">
<el-radio v-model="radio" label="1">选项B</el-radio>
<el-input placeholder="请输入" v-model="form.name"></el-input>
<i class="icon el-icon-circle-plus-outline"></i>
<i class="icon el-icon-remove-outline"></i>
</div>
</div>
</el-form-item>
<el-form-item prop="userName" label="解析">
<Ueditor ref="title1" @ready="editorReady" />
</el-form-item>
<el-form-item prop="userName" label="上传题干文件">
<el-input placeholder="请输入题库名称" v-model="form.name"></el-input>
</el-form-item>
<el-form-item prop="userName" label="支持学生上传附件">
<el-radio-group v-model="radio">
<el-radio :label="3"></el-radio>
<el-radio :label="6"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="userName" label="判分标准">
<el-select style="width: 100%" v-model="form.provinceId" placeholder="请选择判分标准">
<el-option v-for="(item, i) in types" :key="i" :label="item.provinceName"
:value="item.provinceId"></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="quesSubmit(0)">保存</el-button>
<el-button type="primary" @click="quesSubmit(1)">保存并继续新增</el-button>
<el-button @click="quesVisible = false">取消</el-button>
</span>
</el-dialog>
<el-dialog :visible.sync="richEditor.dialogVisible" width="1000px" :show-close="false" center
:close-on-click-modal="false">
<Ueditor ref="title2" @ready="editorReady" />
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="editorSubmit">确定</el-button>
<el-button @click="richEditor.dialogVisible = false">取消</el-button>
</span>
</el-dialog>
<el-dialog title="批量导入试题" :visible.sync="importVisible" width="520px" :close-on-click-modal="false"
:modal-append-to-body="false">
<el-form label-width="110px">
<el-form-item prop="userName" label="题库分类">
<el-select style="width: 100%" v-model="form.provinceId" placeholder="请选择题库分类">
<el-option v-for="(item, i) in types" :key="i" :label="item.provinceName"
:value="item.provinceId"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="userName" label="当前题库">
<el-input placeholder="请输入题库名称" v-model="form.name" disabled></el-input>
</el-form-item>
<el-form-item prop="userName" label="模板文件">
<el-button type="primary" @click="download">模板下载<i class="el-icon-download el-icon--right"></i></el-button>
</el-form-item>
<el-form-item prop="userName" label="选择文件上传">
<el-upload name="file" accept=".xls,.xlsx" ref="upload" class="import-file" drag :before-upload="beforeUpload"
:on-remove="handleRemove" :on-error="uploadError" :on-success="uploadSuccess" :before-remove="beforeRemove"
:limit="1" :data="{
// competitionId: id,
platformId: 2
}" :disabled="uploading" :on-exceed="handleExceed" :action="this.api.batchImportPersonalData"
:file-list="uploadList" :headers="headers">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip" style="line-height: 1.8;">
<p>上传说明</p>
<p>1.请按照模板要求正确填写后上传</p>
<p>2.上传文件大小限制50M</p>
<p>3.仅支持上传.xls .xlsx文件格式</p>
</div>
</el-upload>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="importVisible = false">关闭</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import Util from '@/libs/util'
import Setting from '@/setting'
import Ueditor from '@/components/ueditor'
import Breadcrumb from '@/components/breadcrumb'
export default {
components: { Ueditor, Breadcrumb },
data () {
return {
crumbs: [
{
name: this.$route.query.name || '题库管理',
route: '/quesBank'
},
{
name: '试题管理'
},
],
typeId: this.$route.query.id,
loading: false,
createSource: 1,
keyword: '',
radio: '',
type: 1,
types: [],
status: [
{
id: 1,
name: '启用'
},
{
id: 2,
name: '禁用'
},
],
filter: {
status: '',
keyWord: '',
},
list: [],
page: 1,
pageSize: 10,
total: 0,
multipleSelection: [],
form: {
userName: '',
provinceId: '',
cityId: '',
roleList: []
},
richEditor: {
dialogVisible: false,
object: null,
parameterName: '',
instance: null
},
rules: {
userName: [
{ required: true, message: "请输入姓名", trigger: "blur" }
],
provinceId: [
{ required: true, message: '请选择省份', trigger: "change" }
],
cityId: [
{ required: true, message: '请选择城市', trigger: "change" }
],
roleList: [
{ required: true, message: '请选择角色', trigger: "change" }
],
},
quesVisible: false,
submiting: false, //
setKey: '',
transferVisible: false,
chooseVisible: false,
members: [],
choosePartnerId: '',
curRow: '',
provinces: [],
cities: [],
editVisible: false,
importVisible: false,
uploadList: [],
uploadFaild: false,
uploadTips: '',
exportCode: '',
headers: {
token: Util.local.get(Setting.tokenKey)
},
uploading: false,
};
},
watch: {
keyword: function (val) {
clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(this.getType, 500);
},
keyWord: function (val) {
clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(this.initData, 500);
}
},
mounted () {
this.getType()
},
methods: {
//
async getType () {
try {
this.loading = true
const { data } = await this.$post(this.api.TreeStructure, {
createSource: 1,
questionBankId: this.typeId,
keyword: this.keyword,
})
this.types = data
// this.getList()
} finally {
this.loading = false
}
},
//
toSet () {
this.$router.push(`/knowledge?id=${this.typeId}`)
},
//
typeChange () {
this.$refs.orgTree.setCurrentKey(null)
this.curTeamId = ''
this.initData()
},
// id
getTeamId (list) {
for (const i in list) {
const e = list[i]
if (e.isTeam && !this.curTeamId) {
this.curTeamId = e.id
break
} else {
this.getTeamId(e.children)
}
}
},
//
handleNodeClick (data) {
this.type = ''
this.curTeamId = ''
if (data.isTeam) {
this.curTeamId = data.id
} else {
// this.getTeamId(data.children)
}
if (!this.curTeamId) this.curTeamId = data.id
this.initData()
this.$refs.table.clearSelection()
},
//
getAll () {
this.curTeamId = ''
this.getList()
},
//
getList () {
this.$post(this.api[this.type ? 'partnerAccountMergeList' : 'partnerAccountList'], {
type: this.type || 1,
partnerClassificationId: this.curTeamId,
pageNum: this.page,
pageSize: this.pageSize
}).then(({ pageList }) => {
pageList.records.forEach((e, i) => {
e.id = i
})
this.list = pageList.records
this.total = pageList.total
}).catch(err => { })
},
//
currentChange (val) {
this.page = val
this.getList()
},
handleSelectionChange (val) { //
this.multipleSelection = val
},
initData () {
this.$refs.table.clearSelection()
this.page = 1
this.getList()
},
//
del (row) {
this.$confirm("确定要删除吗?", "提示", {
type: "warning"
}).then(() => {
this.$post(`${this.api.delPartnerAccount}?accountId=${row.accountId}`).then(res => {
Util.successMsg("删除成功")
this.getList()
}).catch(res => { })
}).catch(() => { })
},
async switchOff (val, row) {
this.$post(this.api.disabledEventsCompetition, {
competitionId: row.id,
isOpen: val,
type: 0 // (01)
}).then(res => {
Util.successMsg(val == 1 ? '禁用成功' : '启用成功')
}).catch(err => { })
await this.$post(`${this.api.refreshPageNotification}?content=1`)
},
//
edit (row) {
if (!row.provinceId) row.provinceId = ''
if (!row.cityId) row.cityId = ''
row.roleList = row.roleId.split(',').map(e => +e)
this.editVisible = true
this.form = JSON.parse(JSON.stringify(row))
},
//
addOpt (i) {
},
//
delOpt (i) {
},
inputClick (object, parameterName) {
this.richEditor.object = object
this.richEditor.parameterName = parameterName
this.richEditor.dialogVisible = true
},
editorSubmit () {
let content = this.richEditor.instance.getContent()
if (this.richEditor.parameterName === 'title') { //
// if (this.questionItemReset(content)) {
// this.richEditor.object[this.richEditor.parameterName] = content
// this.richEditor.dialogVisible = false
// }
} else {
this.richEditor.object[this.richEditor.parameterName] = content
this.richEditor.dialogVisible = false
}
},
questionItemReset (content) {
let spanRegex = new RegExp('<span class="gapfilling-span (.*?)">(.*?)<\\/span>', 'g')
let _this = this
let newFormItem = []
let gapfillingItems = content.match(spanRegex)
if (gapfillingItems === null) {
this.$message.error('请插入填空')
return false
}
gapfillingItems.forEach(function (span, index) {
let pairRegex = /<span class="gapfilling-span (.*?)">(.*?)<\/span>/
pairRegex.test(span)
newFormItem.push({ id: null, itemUuid: RegExp.$1, prefix: RegExp.$2, content: '', score: '0' })
})
newFormItem.forEach(function (item) {
_this.form.items.some((oldItem, index) => {
if (oldItem.itemUuid === item.itemUuid) {
item.content = oldItem.content
item.id = oldItem.id
item.score = oldItem.score
return true
}
})
})
_this.form.items = newFormItem
return true
},
//
submitEdit () {
this.$refs.form.validate((valid) => {
if (valid) {
if (this.submiting) return false
this.submiting = true
const form = JSON.parse(JSON.stringify(this.form))
form.classificationId = form.partnerClassificationId
this.$post(this.api.editProvinceCity, form).then(res => {
this.getList()
Util.successMsg("编辑成功!")
this.editVisible = false
setTimeout(() => {
this.submiting = false
}, 2000)
}).catch(res => {
setTimeout(() => {
this.submiting = false
}, 2000)
})
}
})
},
//
add () {
this.quesVisible = true
},
editorReady (instance) {
// this.richEditor.instance = instance
// let currentContent = this.richEditor.object[this.richEditor.parameterName]
// this.richEditor.instance.setContent(currentContent)
// // Ueditor
// this.richEditor.instance.focus(true)
},
//
quesSubmit () {
console.log(55, this.richEditor, this.$refs.title.getUEContent(), this.$refs.title1.getUEContent())
},
//
batchImport () {
this.importVisible = true
this.uploadList = []
this.uploadFaild = false
},
//
download () {
location.href = this.api[this.info.completeCompetitionSetup.competitionType ? 'competionTeamTemplate' : 'competionPersonTemplate']
},
//
handleExceed (files, fileList) {
Util.warningMsg(
`当前限制选择 1 个文件,如需更换,请删除上一个文件再重新选择!`
)
},
//
showFaild () {
axios.get(`${this.api.TeamDataExportFailure}?exportCode=${this.exportCode}&platformId=2&type=${this.info.completeCompetitionSetup.competitionType ? 1 : 2}`, {
headers: this.headers,
responseType: 'blob'
}).then((res) => {
const name = res.headers['content-disposition']
Util.downloadFileDirect(name ? decodeURI(name) : '批量导入报名人员失败数据导出.xlsx', new Blob([res.data]))
}).catch(res => { })
},
uploadSuccess ({ data, status }) {
this.uploading = false
this.uploadFaild = false
this.uploadTips = ''
if (status === 200) {
this.init()
const { tip } = data
if (data.exportCode) {
this.exportCode = data.exportCode
this.uploadFaild = true
this.uploadTips = tip
} else {
Util[tip.includes('5000') ? 'errorMsg' : 'successMsg'](tip, 3000)
this.importVisible = false
}
} else {
Util.errorMsg(res.message || '上传失败,请检查数据', 3000)
}
},
uploadError (err, file, fileList) {
this.uploading = false
this.$message({
message: "上传出错,请重试!",
type: "error",
center: true
})
},
beforeUpload (file) {
this.uploading = true
},
beforeRemove (file, fileList) {
return this.$confirm(`确定移除 ${file.name}`)
},
handleRemove (file, fileList) {
this.uploadList = fileList
this.uploadFaild = false
},
cancelUpload () {
this.uploading = false
this.$refs.upload.abort()
this.keyword = ''
this.init()
this.importVisible = false
},
//
backstageUpload () {
this.isBackstage = 1
this.importVisible = false
},
}
};
</script>
<style lang="scss" scoped>
.page {
display: flex;
padding: 0 24px;
.side {
width: 300px;
padding: 24px 10px 24px 0;
margin-right: 24px;
border-right: 1px solid rgba(0, 0, 0, 0.06);
}
.right {
width: calc(100% - 324px);
padding: 24px 0;
}
}
.tool {
margin-bottom: 0;
.filter {
flex-wrap: wrap;
justify-content: space-between;
li {
margin-bottom: 15px;
}
}
}
/deep/.opts {
.line {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.el-radio {
margin-right: 15px;
}
.icon {
margin-left: 10px;
font-size: 18px;
cursor: pointer;
}
}
</style>

@ -0,0 +1,560 @@
<template>
<div>
<Breadcrumb :data="crumbs" />
<div class="page">
<div class="side">
<div class="m-b-20">
<el-radio-group v-model="isNotJoin" @change="typeChange">
<div class="m-b-20">
<el-radio :label="0">所有试题</el-radio>
</div>
<div>
<el-radio :label="1">未分配知识点的试题</el-radio>
</div>
</el-radio-group>
</div>
<el-divider></el-divider>
<div>
<div class="flex-between m-b-10">
<h6 class="page-name" style="margin-bottom: 0">知识点框架</h6>
<el-button type="text" @click="toSet">设置</el-button>
</div>
<el-input class="m-b-10" placeholder="请输入知识点分类、知识点名称" prefix-icon="el-icon-search" size="small"
v-model="keyword" clearable></el-input>
<div style="height: 504px; max-height: 504px; overflow: auto">
<el-tree :data="types" default-expand-all ref="typeTree" node-key="id" highlight-current
:expand-on-click-node="false" @node-click="handleNodeClick" :props="{ label: 'name', isLeaf: 'leaf' }">
<span class="custom-tree-node" slot-scope="{ node, data }">
<img v-if="data.type" class="m-r-5" src="@/assets/images/knowledge.svg" alt="">
<span class="org-name">{{ data.name }}</span>
</span>
</el-tree>
</div>
</div>
</div>
<div class="right">
<h6 class="page-name">筛选</h6>
<div class="tool">
<ul class="filter">
<li>
<label>题目类型</label>
<el-select v-model="filter.questionTypes" clearable placeholder="请选择题目类型" multiple @change="initData">
<el-option v-for="(item, i) in questionTypes" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</li>
<li>
<label>专业</label>
<el-select v-model="filter.specialtyIds" clearable placeholder="请选择专业" multiple @change="initData">
<el-option v-for="(item, i) in status" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</li>
<li>
<label>年份</label>
<el-date-picker v-model="givenYears" type="year" placeholder="请选择年份" format="yyyy" value-format="yyyy"
multiple @change="initData">
</el-date-picker>
</li>
<li>
<label>试题难度</label>
<el-select v-model="filter.difficultys" clearable placeholder="请选择试题难度" multiple @change="initData">
<el-option v-for="(item, i) in difficults" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</li>
<li>
<label>知识点</label>
<el-cascader :disabled="isNotJoin !== 0" placeholder="请选择知识点" v-model="knowledgePointIds"
:options="knowledges" :props="{ value: 'id', label: 'name', multiple: true }" clearable collapse-tags
@change="initData"></el-cascader>
</li>
<li>
<label>正确率</label>
<el-input style="width: 90px;" placeholder="请输入" v-model="filter.correctRateStart" clearable />
<span style="margin: 0 10px;">% -- </span>
<el-input style="width: 90px;" placeholder="请输入" v-model="filter.correctRateEnd" clearable />
<span style="margin-left: 10px;">%</span>
</li>
<li>
<label>状态</label>
<el-select v-model="filter.status" clearable placeholder="请选择状态" @change="initData">
<el-option v-for="(item, i) in status" :key="i" :label="item.name" :value="item.id"></el-option>
</el-select>
</li>
<li>
<label>搜索</label>
<el-input style="width: 250px;" placeholder="请输入题干" prefix-icon="el-icon-search" v-model="filter.keyword"
clearable />
</li>
<div style="margin-bottom: 15px;">
<el-button type="primary" @click="add">新增试题</el-button>
<el-button type="primary" @click="batchImport">批量导入</el-button>
<!-- <el-button type="primary" @click="add">批量转移</el-button> -->
<el-button type="primary" @click="add">批量移除 </el-button>
<el-button type="primary" @click="add">批量删除</el-button>
</div>
</ul>
</div>
<el-table :data="list" class="table" ref="table" stripe header-align="center"
@selection-change="handleSelectionChange" row-key="id">
<el-table-column type="selection" width="45" align="center" :reserve-selection="true"></el-table-column>
<el-table-column type="index" width="50" label="序号" align="center"></el-table-column>
<el-table-column prop="stem" label="题干" align="center" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column prop="account" label="题型" align="center" width="100">
<template slot-scope="scope">{{ questionTypes.find(e => e.id === scope.row.questionType) ?
questionTypes.find(e => e.id === scope.row.questionType).name : '' }}</template>
</el-table-column>
<el-table-column prop="professionalName" label="专业" align="center" min-width="120"></el-table-column>
<el-table-column prop="knowledgePointName" label="知识点" align="center" min-width="120"></el-table-column>
<el-table-column prop="givenYear" label="年份" align="center" width="50"></el-table-column>
<el-table-column prop="difficulty" label="难度" align="center" width="50">
<template slot-scope="scope">{{ difficults.find(e => e.id === scope.row.difficulty) ? difficults.find(e =>
e.id === scope.row.difficulty).name : '' }}</template>
</el-table-column>
<el-table-column prop="correctRate" label="正确率" align="center" width="60"></el-table-column>
<el-table-column prop="updateTime" label="更新时间" align="center" width="140"></el-table-column>
<el-table-column prop="lastEditor" label="最近编辑人" align="center" width="90"></el-table-column>
<el-table-column prop="referenceCount" label="已引用试卷(套)" align="center" width="100"></el-table-column>
<el-table-column label="操作" align="center" width="300">
<template slot-scope="scope">
<el-button type="text" @click="copy(scope.row)">复制</el-button>
<el-button type="text" @click="toDetail(scope.row, 1)">查看</el-button>
<el-button type="text" @click="toDetail(scope.row)">编辑</el-button>
<el-button type="text" @click="del(scope.row)">移除</el-button>
<el-button type="text" @click="del(scope.row)">删除</el-button>
<el-switch v-model="scope.row.status" :active-value="1" :inactive-value="0" style="margin: 0 10px 0 5px"
:active-text="scope.row.status ? '开' : '关'"
@change="switchOff($event, scope.row, scope.$index)"></el-switch>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination background @current-change="currentChange" :current-page="page"
layout="total, prev, pager, next" :total="total"></el-pagination>
</div>
</div>
</div>
<Detail :visible.sync="quesVisible" :row.sync="curRow" :readonly.sync="readonly" />
<el-dialog title="批量导入试题" :visible.sync="importVisible" width="520px" :close-on-click-modal="false"
:modal-append-to-body="false">
<el-form label-width="110px">
<el-form-item prop="userName" label="题库分类">
<el-select style="width: 100%" v-model="form.provinceId" placeholder="请选择题库分类">
<el-option v-for="(item, i) in types" :key="i" :label="item.provinceName"
:value="item.provinceId"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="userName" label="当前题库">
<el-input placeholder="请输入题库名称" v-model="form.name" disabled></el-input>
</el-form-item>
<el-form-item prop="userName" label="模板文件">
<el-button type="primary" @click="download">模板下载<i class="el-icon-download el-icon--right"></i></el-button>
</el-form-item>
<el-form-item prop="userName" label="选择文件上传">
<el-upload name="file" accept=".xls,.xlsx" ref="upload" class="import-file" drag :before-upload="beforeUpload"
:on-remove="handleRemove" :on-error="uploadError" :on-success="uploadSuccess" :before-remove="beforeRemove"
:limit="1" :data="{
// competitionId: id,
platformId: 2
}" :disabled="uploading" :on-exceed="handleExceed" :action="this.api.batchImportPersonalData"
:file-list="uploadList" :headers="headers">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip" style="line-height: 1.8;">
<p>上传说明</p>
<p>1.请按照模板要求正确填写后上传</p>
<p>2.上传文件大小限制50M</p>
<p>3.仅支持上传.xls .xlsx文件格式</p>
</div>
</el-upload>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="importVisible = false">关闭</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import Util from '@/libs/util'
import Setting from '@/setting'
import Ueditor from '@/components/ueditor'
import Breadcrumb from '@/components/breadcrumb'
import Detail from '../detail'
import Const from '@/const/ques'
export default {
components: { Ueditor, Breadcrumb, Detail },
data () {
return {
crumbs: [
{
name: this.$route.query.questionBankName || '题库管理',
route: '/quesBank'
},
{
name: '试题管理'
},
],
difficults: Const.difficults,
questionTypes: Const.questionTypes,
typeId: this.$route.query.id,
loading: false,
isNotJoin: 0,
createSource: 1,
keyword: '',
type: 1,
types: [],
status: [
{
id: 1,
name: '启用'
},
{
id: 0,
name: '禁用'
},
],
knowledges: [],
givenYears: '',
knowledgePointIds: [],
filter: {
questionTypes: [],
correctRateEnd: '',
correctRateStart: '',
difficultys: [],
specialtyIds: [],
status: '',
keyword: '',
},
list: [],
page: 1,
pageSize: 10,
total: 0,
multipleSelection: [],
quesVisible: false,
curRow: {},
readonly: false,
form: {},
importVisible: false,
uploadList: [],
uploadFaild: false,
uploadTips: '',
exportCode: '',
headers: {
token: Util.local.get(Setting.tokenKey)
},
uploading: false,
};
},
watch: {
keyword: function (val) {
clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(this.getType, 500);
},
'filter.keyword': function (val) {
clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(this.initData, 500);
},
quesVisible () {
this.quesVisible || this.getList()
}
},
mounted () {
this.getType()
this.getKnowledge()
},
methods: {
//
async getType () {
try {
this.loading = true
const { data } = await this.$post(this.api.TreeStructure, {
createSource: 1,
questionBankId: this.typeId,
keyword: this.keyword,
})
this.types = data
this.getList()
} finally {
this.loading = false
}
},
//
toSet () {
this.$router.push(`/knowledge?id=${this.typeId}`)
},
//
typeChange () {
this.$refs.typeTree.setCurrentKey(null)
this.initData()
},
//
handleNodeClick () {
this.isNotJoin = ''
this.initData()
this.$refs.table.clearSelection()
},
//
handleType (list) {
list.map(e => {
if (e.children && e.children.length) {
this.handleType(e.children)
} else {
delete e.children
}
})
},
//
async getKnowledge () {
try {
const { data } = await this.$post(this.api.TreeStructure, {
createSource: 1,
questionBankId: this.typeId,
keyword: ''
})
this.handleType(data)
this.knowledges = data
} catch (e) { }
},
//
async getList () {
let type = this.$refs.typeTree.getCurrentKey()
const k = this.knowledgePointIds
if (k.length) { //
type = k.map(e => {
return e[e.length - 1]
})
} else if (type) {
type = [type]
}
const { message } = await this.$post(this.api.listQuestion, {
...this.filter,
givenYears: this.givenYears ? [this.givenYears] : [],
isNotJoin: this.isNotJoin || '',
pageNum: this.page,
pageSize: this.pageSize,
questionBankId: this.typeId,
knowledgePointIds: type || [],
})
this.list = Util.removeTag(message.records)
this.total = message.total
},
//
currentChange (val) {
this.page = val
this.getList()
},
handleSelectionChange (val) { //
this.multipleSelection = val
},
initData () {
this.$refs.table.clearSelection()
this.page = 1
this.getList()
},
//
add () {
this.quesVisible = true
this.curRow = {}
this.readonly = false
},
//
copy (row) {
},
// /
toDetail (row, readonly = false) {
this.quesVisible = true
this.curRow = row
this.readonly = readonly
},
//
del (row) {
this.$confirm("确定要删除吗?", "提示", {
type: "warning"
}).then(() => {
this.$post(`${this.api.delPartnerAccount}?accountId=${row.accountId}`).then(res => {
Util.successMsg("删除成功")
this.getList()
}).catch(res => { })
}).catch(() => { })
},
async switchOff (val, row) {
this.$post(this.api.disabledEventsCompetition, {
competitionId: row.id,
isOpen: val,
type: 0 // (01)
}).then(res => {
Util.successMsg(val == 1 ? '禁用成功' : '启用成功')
}).catch(err => { })
await this.$post(`${this.api.refreshPageNotification}?content=1`)
},
//
batchImport () {
this.importVisible = true
this.uploadList = []
this.uploadFaild = false
},
//
download () {
location.href = this.api[this.info.completeCompetitionSetup.competitionType ? 'competionTeamTemplate' : 'competionPersonTemplate']
},
//
handleExceed (files, fileList) {
Util.warningMsg(
`当前限制选择 1 个文件,如需更换,请删除上一个文件再重新选择!`
)
},
//
showFaild () {
axios.get(`${this.api.TeamDataExportFailure}?exportCode=${this.exportCode}&platformId=2&type=${this.info.completeCompetitionSetup.competitionType ? 1 : 2}`, {
headers: this.headers,
responseType: 'blob'
}).then((res) => {
const name = res.headers['content-disposition']
Util.downloadFileDirect(name ? decodeURI(name) : '批量导入报名人员失败数据导出.xlsx', new Blob([res.data]))
}).catch(res => { })
},
uploadSuccess ({ data, status }) {
this.uploading = false
this.uploadFaild = false
this.uploadTips = ''
if (status === 200) {
this.init()
const { tip } = data
if (data.exportCode) {
this.exportCode = data.exportCode
this.uploadFaild = true
this.uploadTips = tip
} else {
Util[tip.includes('5000') ? 'errorMsg' : 'successMsg'](tip, 3000)
this.importVisible = false
}
} else {
Util.errorMsg(res.message || '上传失败,请检查数据', 3000)
}
},
uploadError (err, file, fileList) {
this.uploading = false
this.$message({
message: "上传出错,请重试!",
type: "error",
center: true
})
},
beforeUpload (file) {
this.uploading = true
},
beforeRemove (file, fileList) {
return this.$confirm(`确定移除 ${file.name}`)
},
handleRemove (file, fileList) {
this.uploadList = fileList
this.uploadFaild = false
},
cancelUpload () {
this.uploading = false
this.$refs.upload.abort()
this.keyword = ''
this.init()
this.importVisible = false
},
//
backstageUpload () {
this.isBackstage = 1
this.importVisible = false
},
}
};
</script>
<style lang="scss" scoped>
.page {
display: flex;
padding: 0 24px;
.side {
width: 300px;
padding: 24px 10px 24px 0;
margin-right: 24px;
border-right: 1px solid rgba(0, 0, 0, 0.06);
}
.right {
width: calc(100% - 324px);
padding: 24px 0;
}
}
.tool {
margin-bottom: 0;
.filter {
flex-wrap: wrap;
justify-content: space-between;
li {
margin-bottom: 15px;
}
}
}
/deep/.opts {
.line {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.el-radio {
margin-right: 15px;
}
.icon {
margin-left: 10px;
font-size: 18px;
cursor: pointer;
}
}
/deep/.fill-blanks {
.fills {
display: inline-flex;
align-items: center;
}
.fill-item {
display: inline-flex;
align-items: center;
margin-right: 10px;
.el-input {
width: 100px;
margin-right: 10px;
}
}
.add-fill {
margin-left: 10px;
}
.score-wrap {
display: inline-flex;
align-items: center;
.el-input {
width: 80px;
margin: 0 10px;
}
}
}
</style>

@ -91,7 +91,7 @@
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="quesBankVisible = false">取消</el-button> <el-button @click="quesBankVisible = false">取消</el-button>
<el-button type="primary" v-loading="submiting" @click="quesBankSubmit"> </el-button> <el-button type="primary" :loading="submiting" @click="quesBankSubmit">确定</el-button>
</span> </span>
</el-dialog> </el-dialog>
</div> </div>
@ -194,7 +194,7 @@ export default {
this.initData() this.initData()
}, },
// //
handleNodeClick (data) { handleNodeClick () {
this.isNotJoin = '' this.isNotJoin = ''
this.initData() this.initData()
this.$refs.table.clearSelection() this.$refs.table.clearSelection()
@ -270,7 +270,7 @@ export default {
}, },
// //
toQues (row) { toQues (row) {
this.$router.push(`/ques?id=${row.id}&name=${row.questionBankName}`) this.$router.push(`/ques?id=${row.id}&questionBankName=${row.questionBankName}&questionBankCategory=${row.questionBankCategory}`)
}, },
// / // /
async edit (row, isCopy) { async edit (row, isCopy) {

@ -53,7 +53,7 @@
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="typeVisible = false">取消</el-button> <el-button @click="typeVisible = false">取消</el-button>
<el-button type="primary" v-loading="submiting" @click="typeSubmit">确定</el-button> <el-button type="primary" :loading="submiting" @click="typeSubmit">确定</el-button>
</span> </span>
</el-dialog> </el-dialog>

@ -30,7 +30,7 @@
</el-form-item> </el-form-item>
</div> </div>
<div class="item-line"> <div class="item-line">
<el-form-item prop="columnId" label="所属专业"> <el-form-item prop="professionals" label="所属专业">
<el-select v-model="form.professionalId" clearable placeholder="请选择所属专业"> <el-select v-model="form.professionalId" clearable placeholder="请选择所属专业">
<el-option v-for="(item, i) in professionals" :key="i" :label="item.professionalName" <el-option v-for="(item, i) in professionals" :key="i" :label="item.professionalName"
:value="item.professionalId" filter></el-option> :value="item.professionalId" filter></el-option>
@ -102,7 +102,7 @@
</div> </div>
<div> <div>
<el-button type="primary" @click="submit(0)">一键分配分值</el-button> <el-button type="primary" @click="submit(0)">一键分配分值</el-button>
<el-button type="primary" @click="submit(1)">批量添加</el-button> <el-button type="primary" @click="showQuesDia(item)">批量添加</el-button>
<el-button type="danger" @click="submit(1)">批量删除试题</el-button> <el-button type="danger" @click="submit(1)">批量删除试题</el-button>
</div> </div>
</div> </div>
@ -119,6 +119,7 @@
</div> </div>
<Template :visible.sync="templateVisible" /> <Template :visible.sync="templateVisible" />
<Manual :visible.sync="quesVisible" />
</div> </div>
</template> </template>
<script> <script>
@ -127,8 +128,9 @@ import Util from '@/libs/util'
import Ueditor from '@/components/ueditor' import Ueditor from '@/components/ueditor'
import Breadcrumb from '@/components/breadcrumb' import Breadcrumb from '@/components/breadcrumb'
import Template from './template' import Template from './template'
import Manual from './manual'
export default { export default {
components: { Ueditor, Breadcrumb, Template }, components: { Ueditor, Breadcrumb, Template, Manual },
data () { data () {
return { return {
crumbs: [ crumbs: [
@ -277,12 +279,15 @@ export default {
], ],
}, },
templateVisible: false, templateVisible: false,
quesVisible: false,
}; };
}, },
computed: { computed: {
//
questionCount () { questionCount () {
return this.form.paperOutline.reduce((e, j) => (e += +j.questionNum), 0) return this.form.paperOutline.reduce((e, j) => (e += +j.questionNum), 0)
}, },
//
score () { score () {
return this.form.paperOutline.reduce((e, j) => (e += +j.targetScore), 0) return this.form.paperOutline.reduce((e, j) => (e += +j.targetScore), 0)
} }
@ -348,6 +353,10 @@ export default {
delLine (i) { delLine (i) {
this.form.paperOutline.length > 1 && this.form.paperOutline.splice(i, 1) this.form.paperOutline.length > 1 && this.form.paperOutline.splice(i, 1)
}, },
//
showQuesDia () {
this.quesVisible = true
},
// //
submit () { submit () {

@ -2,21 +2,71 @@
<div> <div>
<el-dialog title="批量添加单选题" :visible.sync="quesVisible" width="1200px" :close-on-click-modal="false" <el-dialog title="批量添加单选题" :visible.sync="quesVisible" width="1200px" :close-on-click-modal="false"
@closed="closeDia"> @closed="closeDia">
<div class="tool"> <div class="wrap">
<ul class="filter"> <!-- 题库 -->
<li> <div class="item">
<label>搜索</label> <el-input class="m-b-10" placeholder="请输入题库分类/题库名称" prefix-icon="el-icon-search" v-model="quesBankKeyword"
<el-input style="width: 250px;" placeholder="请输入模板名称" prefix-icon="el-icon-search" v-model="keyword"
clearable /> clearable />
</li> <el-tree node-key="id" default-expand-all ref="quesBank" :data="quesBanks" :props="{ label: 'name' }"
</ul> show-checkbox @node-click="getKnowledge" @check-change="quesBankCheck"></el-tree>
<div> </div>
<el-button type="primary" @click="add">新增模板</el-button> <!-- 知识点 -->
<div class="item">
<el-input class="m-b-10" placeholder="请输入知识点分类/知识点名称" prefix-icon="el-icon-search" v-model="knowledgeKeyword"
clearable />
<el-tree :data="knowledges" default-expand-all ref="knowledge" node-key="id" highlight-current
:expand-on-click-node="false" show-checkbox @node-click="knowledgeClick" @check-change="knowledgeCheck"
:props="{ label: 'name' }">
<span class="custom-tree-node" slot-scope="{ node, data }">
<img v-if="data.type" class="m-r-5" src="@/assets/images/knowledge.svg" alt="">
<span class="org-name">{{ data.name }}</span>
</span>
</el-tree>
</div>
<!-- 题目 -->
<div class="item">
<p class="total">单选题{{ ques.length }}道题</p>
<div class="ques">
<div class="line">
<el-checkbox v-model="checked"></el-checkbox>
<span class="serial">序号</span>
<span>题干</span>
</div>
<div v-for="(item, i) in ques" :key="i" class="line">
<el-checkbox v-model="checked"></el-checkbox>
<span class="serial">{{ i + 1 }}</span>
<span>题干</span>
</div>
</div>
</div> </div>
<!-- 已选试题 -->
<div class="item">
<p class="total">已选试题{{ ques.length }}道题</p>
<div class="ques">
<div class="line">
<el-checkbox v-model="checked"></el-checkbox>
<span class="serial">序号</span>
<span>题干</span>
</div> </div>
<div v-for="(item, i) in ques" :key="i" class="line">
<el-checkbox v-model="checked"></el-checkbox>
<span class="serial">{{ i + 1 }}</span>
<span>题干</span>
</div>
</div>
</div>
</div>
<div class="flex j-between">
<p>未找到试题<a class="link" @click="toAddQues">去新增</a></p>
<p>已选题数/目标题数0/10&emsp;&emsp;<a class="link" @click="toEditQues">修改目标</a></p>
</div>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="quesVisible = false">关闭</el-button> <el-button @click="quesVisible = false">取消</el-button>
<el-button type="primary" v-loading="submiting" @click="submit">确定</el-button>
</span> </span>
</el-dialog> </el-dialog>
</div> </div>
@ -29,57 +79,87 @@ export default {
props: ['visible'], props: ['visible'],
data () { data () {
return { return {
checked: false,
quesVisible: false, quesVisible: false,
keyword: '', quesBankKeyword: '',
knowledgeKeyword: '',
searchTimer: null, searchTimer: null,
quesBanks: [],
knowledges: [],
ques: [{}, {}],
submiting: false, submiting: false,
}; };
}, },
watch: { watch: {
'keyword': function (val) { 'quesBankKeyword': function (val) {
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(this.getQuesBank, 500)
},
'knowledgeKeyword': function (val) {
clearTimeout(this.searchTimer) clearTimeout(this.searchTimer)
// this.searchTimer = setTimeout(this.initData, 500) this.searchTimer = setTimeout(this.getKnowledge, 500)
}, },
visible () { visible () {
this.quesVisible = this.visible this.quesVisible = this.visible
this.visible && this.getList() this.visible && this.init()
} }
}, },
mounted () { mounted () {
}, },
methods: { methods: {
// init () {
async getList () { this.getQuesBank()
},
//
async getQuesBank () {
try { try {
const res = await this.$post(this.api.examPaperTemplateList, { const { data } = await this.$post(this.api.getAllQuestionBankCategories, {
pageNum: this.page, keyword: this.quesBankKeyword,
pageSize: this.pageSize, createSource: 1,
...this.filter
}) })
this.list = res.pageList.records this.quesBanks = data
this.total = res.pageList.total
} catch (e) { } } catch (e) { }
}, },
// //
add () { async getKnowledge () {
this.form = _.cloneDeep(this.originForm) try {
this.detailVisible = true const id = this.$refs.quesBank.getCurrentKey()
}, if (id) {
// const { data } = await this.$post(this.api.TreeStructure, {
addLine (i) { createSource: 1,
this.form.paperOutline.splice(i + 1, 0, { questionBankId: id,
examQuestions: [], keyword: this.knowledgeKeyword,
outlineName: '',
questionNum: '',
questionType: '',
targetScore: '',
}) })
this.knowledges = data
}
} catch (e) { }
},
//
quesBankCheck (data, checked) {
// debugger
if (checked) {
// this.$refs.quesBank.setCurrentKey(data.id)
this.getKnowledge()
}
this.$refs.knowledge.setCheckedNodes(checked ? this.knowledges : [])
},
//
knowledgeClick (data) {
},
//
knowledgeCheck () {
},
//
toAddQues () {
}, },
// //
delLine (i) { toEditQues () {
this.form.paperOutline.length > 1 && this.form.paperOutline.splice(i, 1)
}, },
// 使 // 使
async useTemplate (row) { async useTemplate (row) {
@ -165,9 +245,45 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.action-icon { .wrap {
margin-right: 10px; display: flex;
font-size: 18px; margin-bottom: 10px;
border: 1px solid #eee;
.item {
width: 25%;
max-height: calc(100vh - 190px);
padding: 15px;
border-right: 1px solid #eee;
overflow: auto;
&:last-child {
border-right: 0;
}
}
.total {
margin-bottom: 10px;
font-size: 16px;
color: #333;
}
.ques {
.line {
display: flex;
align-items: center;
margin: 5px 0;
}
.serial {
width: 32px;
margin: 0 12px;
text-align: center;
}
}
}
.link {
color: $main-color; color: $main-color;
cursor: pointer; cursor: pointer;
} }

@ -148,7 +148,6 @@
</div> </div>
</div> </div>
<el-dialog title="提示" :visible.sync="delVisible" width="400px" :close-on-click-modal="false" custom-class="del-dia"> <el-dialog title="提示" :visible.sync="delVisible" width="400px" :close-on-click-modal="false" custom-class="del-dia">
<div class="del-wrap"> <div class="del-wrap">
<div class="icon el-icon-warning"></div> <div class="icon el-icon-warning"></div>
@ -224,7 +223,7 @@ export default {
total: 0, total: 0,
multipleSelection: [], multipleSelection: [],
submiting: false, // submiting: false,
delVisible: false, delVisible: false,

@ -93,7 +93,7 @@
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="quesBankVisible = false"> </el-button> <el-button @click="quesBankVisible = false"> </el-button>
<el-button type="primary" @click="quesBankSubmit"> </el-button> <el-button type="primary" :loading="submiting" @click="quesBankSubmit"> </el-button>
</span> </span>
</el-dialog> </el-dialog>
</div> </div>

@ -55,7 +55,7 @@
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="typeVisible = false">取消</el-button> <el-button @click="typeVisible = false">取消</el-button>
<el-button type="primary" v-loading="submiting" @click="typeSubmit">确定</el-button> <el-button type="primary" :loading="submiting" @click="typeSubmit">确定</el-button>
</span> </span>
</el-dialog> </el-dialog>

@ -4,12 +4,15 @@ const meta = {}
export default { export default {
path: '/ques', path: '/ques',
redirect: {
path: `/ques/list`
},
meta, meta,
component: BasicLayout, component: BasicLayout,
children: [ children: [
{ {
path: '/ques', path: 'list',
component: () => import('@/pages/ques'), component: () => import('@/pages/ques/list'),
meta: { title: '试题管理' } meta: { title: '试题管理' }
}, },
] ]

Loading…
Cancel
Save