提交 99a7db3e authored 作者: lihuihui's avatar lihuihui
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
v-model="q" v-model="q"
:data="ExamParerList" :data="ExamParerList"
:filterMethod="filterMethod" :filterMethod="filterMethod"
:titles="['备选栏', '已选栏']"
> >
</el-transfer> </el-transfer>
<el-card style="margin-top: 30px"> <el-card style="margin-top: 30px">
......
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
<span style="margin-top: 20px" <span style="margin-top: 20px"
>您可以根据IP地址限制考生登录位置,只允许给定IP地址的考生登录考试。<el-input >您可以根据IP地址限制考生登录位置,只允许给定IP地址的考生登录考试。<el-input
v-model="config.ip_limits" v-model="config.ip_limits"
placeholder="如允许多个IP地址,请用逗号隔开" placeholder="如允许多个IP地址,请用英文逗号隔开"
style="width: 230px" style="width: 250px"
:disabled="config.enabled_ip_limit === false" :disabled="config.enabled_ip_limit === false"
></el-input> ></el-input>
您当前所在网络的IP地址是:{{ ip }}</span 您当前所在网络的IP地址是:{{ ip }}</span
...@@ -26,31 +26,36 @@ ...@@ -26,31 +26,36 @@
<el-row> <el-row>
考试中:<el-checkbox v-model="config.enabled_lock" style="margin-left: 20px">锁定考试 </el-checkbox> 考试中:<el-checkbox v-model="config.enabled_lock" style="margin-left: 20px">锁定考试 </el-checkbox>
<div style="margin-left: 120px"> <div style="margin-left: 120px">
记录考生登录考试次数,只允许登录<el-input <div>
style="width: 100px" 记录考生登录考试次数,只允许登录<el-input
v-model="config.max_login_times" style="width: 100px"
:disabled="config.enabled_lock === false" v-model="config.max_login_times"
></el-input :disabled="config.enabled_lock === false"
> ></el-input
>
</div>
<div>当登录考试次数超过限定次数,系统会阻止考生登录考试</div> <div>当登录考试次数超过限定次数,系统会阻止考生登录考试</div>
<el-checkbox v-model="config.enabled_web_login" :disabled="config.enabled_lock === false" <el-checkbox v-model="config.enabled_web_login" :disabled="config.enabled_lock === false"
>网页考试</el-checkbox >网页考试</el-checkbox
> >
记录考生离开考试页面次数,每超过<el-input <br />
<span>记录考生离开考试页面次数,每超过</span>
<el-input
v-model="config.leave_interval" v-model="config.leave_interval"
style="width: 100px" style="width: 100px"
:disabled="config.enabled_web_login === false" :disabled="config.enabled_lock === false"
></el-input ></el-input>
>秒计为离开1次,只允许离开<el-input <span>秒计为离开1次,只允许离开</span>
<el-input
style="width: 100px" style="width: 100px"
v-model="config.max_leave_times" v-model="config.max_leave_times"
:disabled="config.enabled_web_login === false" :disabled="config.enabled_lock === false"
></el-input ></el-input
> >
<div>当登录考试次数超过限定次数,系统会阻止考生登录考试</div> <div>当登录考试次数超过限定次数,系统会阻止考生登录考试</div>
</div> </div>
</el-row> </el-row>
<el-row style="margin-left: 73px"> <el-row style="margin: 10px 0 0 73px">
<el-checkbox v-model="config.enabled_watermark">答题水印 </el-checkbox> <el-checkbox v-model="config.enabled_watermark">答题水印 </el-checkbox>
<div style="margin-left: 50px"> <div style="margin-left: 50px">
考生作答页面,使用场次唯一编号和考生准考证号作为背景水印。<br />考生作答过程中截屏或者偷录考试内容,可凭此水印追溯到录屏信息。 考生作答页面,使用场次唯一编号和考生准考证号作为背景水印。<br />考生作答过程中截屏或者偷录考试内容,可凭此水印追溯到录屏信息。
......
...@@ -14,7 +14,13 @@ ...@@ -14,7 +14,13 @@
</el-table-column> </el-table-column>
<el-table-column prop="required" label="必填"> <el-table-column prop="required" label="必填">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch v-model="scope.row.required" active-color="#13ce66" inactive-color="#ff4949"> </el-switch> <el-switch
v-model="scope.row.required"
active-color="#13ce66"
inactive-color="#ff4949"
@change="requiredChange(scope)"
>
</el-switch>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="sort" label="权重"> <el-table-column prop="sort" label="权重">
...@@ -116,6 +122,14 @@ export default { ...@@ -116,6 +122,14 @@ export default {
}, },
methods: { methods: {
// 必填时,允许编辑,考生可见
requiredChange(scope) {
if (scope.row.required === true) {
scope.row.enabled_edit = true
scope.row.enabled_visible = true
}
},
// 下一步
nextStep() { nextStep() {
const info = {} const info = {}
info.name = this.tableData[0] info.name = this.tableData[0]
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
</el-form> </el-form>
<div class="buttons"> <div class="buttons">
<el-button type="primary" @click="prevStep">上一步</el-button> <el-button type="primary" @click="prevStep">上一步</el-button>
<el-button type="primary" style="margin-left: 20px" @click="handleSave">保存</el-button> <el-button type="primary" style="margin-left: 20px" :disabled="loading" @click="handleSave">保存</el-button>
</div> </div>
</div> </div>
<BatchSetting <BatchSetting
...@@ -76,7 +76,8 @@ export default { ...@@ -76,7 +76,8 @@ export default {
questions: [], // 选择的完整试题列表 questions: [], // 选择的完整试题列表
questionTypeGroups: {}, // 试题类型组 questionTypeGroups: {}, // 试题类型组
visible: false, visible: false,
batchSettingQuestionType: null // 批量设置试题类型 batchSettingQuestionType: null, // 批量设置试题类型
loading: false
} }
}, },
computed: { computed: {
...@@ -87,7 +88,7 @@ export default { ...@@ -87,7 +88,7 @@ export default {
// 已添加的试题总分 // 已添加的试题总分
addedQuestionsScore() { addedQuestionsScore() {
return this.addedQuestions.reduce((total, item) => { return this.addedQuestions.reduce((total, item) => {
return total + parseInt(item.score) return total + parseFloat(item.score)
}, 0) }, 0)
} }
}, },
...@@ -111,30 +112,35 @@ export default { ...@@ -111,30 +112,35 @@ export default {
}, },
// 批量获取试题列表,根据选中的试题 // 批量获取试题列表,根据选中的试题
batchGetQuestionList() { batchGetQuestionList() {
this.loading = true
const ids = this.multipleSelection.map(item => item.id) const ids = this.multipleSelection.map(item => item.id)
batchGetQuestionList({ ids }).then(res => { batchGetQuestionList({ ids })
this.questions = res.data .then(res => {
this.questionTypeGroups = this.questions.reduce((result, item) => { this.questions = res.data
const map = { 1: '单选题', 2: '多选题', 3: '问答题', 5: '案例题', 6: '判断题', 7: '实操题', 8: '情景题' } this.questionTypeGroups = this.questions.reduce((result, item) => {
if (result[item.question_type]) { const map = { 1: '单选题', 2: '多选题', 3: '问答题', 5: '案例题', 6: '判断题', 7: '实操题', 8: '情景题' }
result[item.question_type].total += 1 if (result[item.question_type]) {
result[item.question_type].questions.push(item) result[item.question_type].total += 1
} else { result[item.question_type].questions.push(item)
result[item.question_type] = { } else {
total: 1, result[item.question_type] = {
question_type: item.question_type, total: 1,
question_type_name: map[item.question_type], question_type: item.question_type,
score: '0', question_type_name: map[item.question_type],
questions: [item] score: '0',
questions: [item]
}
} }
} return result
return result }, {})
}, {}) })
}) .finally(() => {
this.loading = false
})
}, },
// 是否可以批量设置分数 // 是否可以批量设置分数
canBatchSetting(value) { canBatchSetting(value) {
return [1, 2, 6].includes(value) return [1, 2, 3, 6].includes(value)
}, },
// 批量设置分数 // 批量设置分数
showBatchSetting(data) { showBatchSetting(data) {
......
...@@ -63,12 +63,7 @@ ...@@ -63,12 +63,7 @@
<el-table-column align="center" label="每题分值" width="140"> <el-table-column align="center" label="每题分值" width="140">
<template slot-scope="{ row }"> <template slot-scope="{ row }">
<el-input-number <el-input-number v-model="row.question_score" :min="0" :max="30" style="width: 100%"></el-input-number>
v-model="row.question_score"
:min="0"
step-strictly
style="width: 100%"
></el-input-number>
</template> </template>
</el-table-column> </el-table-column>
...@@ -157,7 +152,6 @@ export default { ...@@ -157,7 +152,6 @@ export default {
question_num: '', question_num: '',
question_score: '', question_score: '',
question_categories: [], question_categories: [],
edit: true,
max_question_num: 0 max_question_num: 0
} }
return { return {
...@@ -185,13 +179,11 @@ export default { ...@@ -185,13 +179,11 @@ export default {
computed: { computed: {
// 试题数量总数 // 试题数量总数
questionTotalNum() { questionTotalNum() {
return this.getQuestionTotalNumberByProp('question_num') return this.getQuestionTotalNumber(this.questionList)
}, },
// 试题总分 // 试题总分
questionTotalScore() { questionTotalScore() {
return this.questionList.reduce((result, item) => { return this.getQuestionTotalScoreNumber(this.questionList)
return result + parseInt(item.question_score) * parseInt(item.question_num)
}, 0)
}, },
// 试题情况 // 试题情况
totalQuestionList() { totalQuestionList() {
...@@ -214,15 +206,15 @@ export default { ...@@ -214,15 +206,15 @@ export default {
return { return {
question_difficulty: item.value, question_difficulty: item.value,
question_difficulty_name: item.label, question_difficulty_name: item.label,
question_num: this.getQuestionTotalNumberByProp('question_num', currentDifficultyQuestionList), question_num: this.getQuestionTotalNumber(currentDifficultyQuestionList),
question_score: this.getQuestionTotalNumberByProp('question_score', currentDifficultyQuestionList) question_score: this.getQuestionTotalScoreNumber(currentDifficultyQuestionList)
} }
}) })
const temp = { const temp = {
question_type: item.question_type, question_type: item.question_type,
question_type_name: this.questionTypeMap.find(type => type.value === item.question_type)?.label, question_type_name: this.questionTypeMap.find(type => type.value === item.question_type)?.label,
question_num: this.getQuestionTotalNumberByProp('question_num', currentTypeQuestionList), question_num: this.getQuestionTotalNumber(currentTypeQuestionList),
question_score: this.getQuestionTotalNumberByProp('question_score', currentTypeQuestionList), question_score: this.getQuestionTotalScoreNumber(currentTypeQuestionList),
children: questionDifficultyList.filter(item => item.question_num) // 难度列表 children: questionDifficultyList.filter(item => item.question_num) // 难度列表
} }
result.push(temp) result.push(temp)
...@@ -232,9 +224,16 @@ export default { ...@@ -232,9 +224,16 @@ export default {
} }
}, },
methods: { methods: {
getQuestionTotalNumberByProp(prop, list = this.questionList) { // 获取试题数量
getQuestionTotalNumber(list = this.questionList) {
return list.reduce((result, item) => { return list.reduce((result, item) => {
return result + parseInt(item[prop]) return result + parseFloat(item.question_num)
}, 0)
},
// 获取试题分数
getQuestionTotalScoreNumber(list = this.questionList) {
return list.reduce((result, item) => {
return result + parseFloat(item.question_score) * parseFloat(item.question_num)
}, 0) }, 0)
}, },
// 新增 // 新增
...@@ -262,6 +261,13 @@ export default { ...@@ -262,6 +261,13 @@ export default {
this.questionList.forEach(question => { this.questionList.forEach(question => {
rules.push(Object.assign({}, question)) rules.push(Object.assign({}, question))
}) })
for (let i = 0; i < rules.length; i++) {
const item = rules[i]
if (!item.question_score || !item.question_num) {
this.$message.error(`第${i + 1}行规则配置错误,请检查后重试`)
return
}
}
const parmas = { id: this.data.id, permission: this.form.permission, rules } const parmas = { id: this.data.id, permission: this.form.permission, rules }
updatePaperRules(parmas).then(res => { updatePaperRules(parmas).then(res => {
this.$message.success('保存成功') this.$message.success('保存成功')
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<el-form-item label="试题类型">{{ data.question_type_name }}</el-form-item> <el-form-item label="试题类型">{{ data.question_type_name }}</el-form-item>
<el-form-item label="数量">{{ data.total }}</el-form-item> <el-form-item label="数量">{{ data.total }}</el-form-item>
<el-form-item label="每道试题分值" prop="score"> <el-form-item label="每道试题分值" prop="score">
<el-input-number v-model="form.score" :min="0" step-strictly :controls="false" /> <el-input-number v-model="form.score" :min="0" :controls="false" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
......
...@@ -12,14 +12,14 @@ ...@@ -12,14 +12,14 @@
> >
</template> </template>
<template v-if="data.paper_type === 2"> <template v-if="data.paper_type === 2">
<el-button type="primary" @click="showSelectQuestion">自动组卷</el-button> <el-button type="primary" @click="handleAutoPaper">自动组卷</el-button>
</template> </template>
</template> </template>
<question-list <question-list
:list="questions" :list="currentQuestions"
:disableScore="data.paper_type === 2" :disableScore="data.paper_type === 2"
style="margin-top: 20px" style="margin-top: 20px"
v-if="questions.length" v-if="currentQuestions.length"
> >
<template v-slot:selection="item" v-if="data.paper_type === 1"> <template v-slot:selection="item" v-if="data.paper_type === 1">
<el-checkbox @change="handleSelectionChange(arguments[0], item)"></el-checkbox> <el-checkbox @change="handleSelectionChange(arguments[0], item)"></el-checkbox>
...@@ -30,9 +30,25 @@ ...@@ -30,9 +30,25 @@
</el-col> </el-col>
<!-- 试题序号 --> <!-- 试题序号 -->
<el-col :span="6" class="is-sticky"> <el-col :span="6" class="is-sticky">
<question-num :list="questions"> <question-num :list="currentQuestions">
<template #footer v-if="data.paper_type === 1"> <template #header>
<el-button type="primary" @click="handleSubmit">保存试卷</el-button> <div class="score">
<el-tooltip>
<div slot="content">所有试题总分</div>
<span class="question-total-number">{{ questionsTotalScore }}</span>
</el-tooltip>
<i>/</i>
<el-tooltip>
<div slot="content">试卷总分</div>
<span class="paper-total-score">{{ data.paper_total_score }}</span>
</el-tooltip>
<em></em>
</div>
</template>
<template #footer>
<template v-if="data.paper_type === 1">
<el-button type="primary" @click="handleSubmit">保存试卷</el-button>
</template>
</template> </template>
</question-num> </question-num>
</el-col> </el-col>
...@@ -81,7 +97,36 @@ export default { ...@@ -81,7 +97,36 @@ export default {
} }
} }
}, },
computed: {
// 有序试题列表
currentQuestions() {
// 试题类型map 1单选 2多选 6判断 3问答 5案例 8情景 7实操
// 试题展示顺序
const arr = [1, 2, 6, 3, 5, 8, 7]
return arr.reduce((result, item) => {
return result.concat(this.questions.filter(question => question.question_type === item))
}, [])
},
// 已添加的试题总分
questionsTotalScore() {
return this.questions.reduce((total, item) => {
return total + parseFloat(item.score)
}, 0)
}
},
methods: { methods: {
// 自动组卷添加试题
handleAutoPaper() {
if (this.questions.length) {
this.$confirm('该试卷已自动组卷完成,再次自动组卷将会清空现有试题,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(this.showSelectQuestion)
} else {
this.showSelectQuestion()
}
},
// 增加试题 // 增加试题
showSelectQuestion() { showSelectQuestion() {
this.visible = true this.visible = true
...@@ -95,7 +140,13 @@ export default { ...@@ -95,7 +140,13 @@ export default {
}, },
// 删除选中试题 // 删除选中试题
handleRemove() { handleRemove() {
this.questions = this.questions.filter(item => !this.multipleSelection.includes(item.id)) this.questions = this.questions.filter(item => {
if (item.children) {
item.children = item.children.filter(item => !this.multipleSelection.includes(item.id))
}
return !this.multipleSelection.includes(item.id)
})
this.multipleSelection = []
}, },
// 保存试卷 // 保存试卷
handleSubmit() { handleSubmit() {
...@@ -108,13 +159,28 @@ export default { ...@@ -108,13 +159,28 @@ export default {
}) })
} }
}) })
// 试题分数为0
for (const item of rules) {
if (!item.score) {
this.$message.error('存在分值为0分的试题,无法保存')
const scoreEl = document.querySelector('#score_' + item.id)
const top = scoreEl.getBoundingClientRect().top
// 滚动到该区域
window.scrollTo(0, top + document.documentElement.scrollTop - 86)
// 光标聚焦
const scoreInput = scoreEl.querySelector('input')
scoreInput.focus()
scoreInput.select()
return
}
}
const parmas = { id: this.data.id, rules } const parmas = { id: this.data.id, rules }
updatePaperRules(parmas).then(res => { updatePaperRules(parmas).then(res => {
this.$message.success('保存成功') this.$message.success('保存成功')
this.$emit('update') this.$emit('update')
}) })
}, },
// 更新详情数据 // 选题组卷更新试题
handleUpdate(list) { handleUpdate(list) {
this.questions = list this.questions = list
} }
...@@ -128,4 +194,19 @@ export default { ...@@ -128,4 +194,19 @@ export default {
top: 84px; top: 84px;
z-index: 100; z-index: 100;
} }
.score {
display: flex;
align-items: flex-end;
justify-content: center;
font-size: 40px;
line-height: 1;
margin-bottom: 20px;
color: #ccc;
em {
font-size: 14px;
}
}
.question-total-number {
color: var(--main-color);
}
</style> </style>
...@@ -10,14 +10,20 @@ ...@@ -10,14 +10,20 @@
v-on="$listeners" v-on="$listeners"
> >
<template v-if="item.children && item.children.length"> <template v-if="item.children && item.children.length">
<question-list :list="item.children" :key="item.id" v-bind="$attrs" v-on="$listeners"> <question-list
<template #selection> :list="item.children"
:key="item.id"
@change="handleChange(item)"
v-bind="$attrs"
v-on="$listeners"
>
<template v-slot:selection="item">
<slot name="selection" v-bind="item"></slot> <slot name="selection" v-bind="item"></slot>
</template> </template>
</question-list> </question-list>
</template> </template>
<template #selection> <template v-slot:selection="item">
<slot name="selection" v-bind="item"></slot> <slot name="selection" v-bind="item"></slot>
</template> </template>
</question-list-item> </question-list-item>
...@@ -30,6 +36,14 @@ import QuestionListItem from './QuestionListItem.vue' ...@@ -30,6 +36,14 @@ import QuestionListItem from './QuestionListItem.vue'
export default { export default {
name: 'QuestionList', name: 'QuestionList',
props: { list: { type: Array, default: () => [] } }, props: { list: { type: Array, default: () => [] } },
components: { QuestionListItem } components: { QuestionListItem },
methods: {
handleChange(data) {
// 直接修改父级分数
data.score = data.children.reduce((total, item) => {
return total + item.score
}, 0)
}
}
} }
</script> </script>
<template> <template>
<el-card style="margin-bottom: 20px" shadow="hover"> <el-card style="margin-bottom: 20px" shadow="hover" :id="`question_${data.id}`">
<div class="question-item"> <div class="question-item">
<div class="question-item-selection"> <div class="question-item-selection">
<slot name="selection"></slot> <slot name="selection" v-bind="data"></slot>
</div> </div>
<div class="question-item-main"> <div class="question-item-main">
<div class="question-item-hd"> <div class="question-item-hd">
<div class="question-item-hd-top"> <div class="question-item-hd-top">
<div class="question-index">{{ index }}</div> <div class="question-index">{{ index }}</div>
<div class="question-type">{{ questionTypeText }}</div> <div class="question-type">{{ questionTypeText }}</div>
<div class="question-score"> <div class="question-score" :id="`score_${data.id}`">
<p>分数:</p> <p>分数:</p>
<el-input-number <el-input-number
v-model="data.score" v-model="data.score"
step-strictly
:controls="false" :controls="false"
:min="0" :min="0"
:disabled="disableScore || hasChildren" :disabled="disableScore || hasChildren"
style="width: 80px" style="width: 80px"
@change="changeScore"
></el-input-number> ></el-input-number>
</div> </div>
</div> </div>
...@@ -58,12 +58,12 @@ ...@@ -58,12 +58,12 @@
</el-radio-group> </el-radio-group>
</template> </template>
</div> </div>
<div class="question-item-ft"> <div class="question-item-ft" v-if="correctAnswerText || data.question_analysis">
<dl> <dl v-if="correctAnswerText">
<dt>正确答案:</dt> <dt>正确答案:</dt>
<dd class="is-answer">{{ correctAnswerText }}</dd> <dd class="is-answer">{{ correctAnswerText }}</dd>
</dl> </dl>
<dl> <dl v-if="data.question_analysis">
<dt>试题解析:</dt> <dt>试题解析:</dt>
<dd><div v-html="data.question_analysis"></div></dd> <dd><div v-html="data.question_analysis"></div></dd>
</dl> </dl>
...@@ -84,9 +84,7 @@ export default { ...@@ -84,9 +84,7 @@ export default {
hasChildren: { type: Boolean, default: false } hasChildren: { type: Boolean, default: false }
}, },
data() { data() {
return { return {}
question: {}
}
}, },
computed: { computed: {
// 试题类型 // 试题类型
...@@ -109,14 +107,15 @@ export default { ...@@ -109,14 +107,15 @@ export default {
}, },
// 处理后的options数据 // 处理后的options数据
currentQuestionOptions() { currentQuestionOptions() {
if (!this.data.question_options) { const options = this.data.question_options || []
if (!options.length) {
return [] return []
} }
return this.data.question_options.map((item, index) => { return options.map((item, index) => {
// 英文字母 + 名称 // 英文字母 + 名称
item.abc = this.A_Z[index] const abc = this.A_Z[index]
item.abc_option = `${this.A_Z[index]}. ${item.option}` const abcOption = `${this.A_Z[index]}. ${item.option}`
return item return { ...item, abc, abc_option: abcOption }
}) })
}, },
// 正确答案显示的英文字母 // 正确答案显示的英文字母
...@@ -127,6 +126,11 @@ export default { ...@@ -127,6 +126,11 @@ export default {
}, []) }, [])
return result.join('、') return result.join('、')
} }
},
methods: {
changeScore() {
this.$emit('change', this.data)
}
} }
} }
</script> </script>
......
<template> <template>
<app-card> <app-card>
<div class="question-num"> <div class="question-num">
<div class="question-num-hd">
<slot name="header"></slot>
</div>
<div class="question-num-bd"> <div class="question-num-bd">
<ul> <dl v-for="item in questionGroups" :key="item.question_type">
<li :class="className" v-for="(item, index) in list" :key="index" @click="handleClick(index)"> <dt>{{ item.question_type_name }}</dt>
{{ index + 1 }} <dd>
</li> <ul>
</ul> <li
v-for="(item, index) in item.children"
:class="genClass(item)"
:key="index"
@click="handleClick(item)"
>
{{ index + 1 }}
</li>
</ul>
</dd>
</dl>
</div> </div>
<div class="question-num-ft"> <div class="question-num-ft">
<slot name="footer"></slot> <slot name="footer"></slot>
...@@ -23,20 +36,37 @@ export default { ...@@ -23,20 +36,37 @@ export default {
} }
}, },
data() { data() {
return { return {}
indexList: [],
currentIndex: 0 // 当前点击题号
}
}, },
computed: { computed: {
className() { questionGroups() {
return {} const map = { 1: '单选题', 2: '多选题', 3: '问答题', 5: '案例题', 6: '判断题', 7: '实操题', 8: '情景题' }
return this.list.reduce((result, item) => {
const temp = {
question_type: item.question_type,
question_type_name: map[item.question_type],
children: [{ ...item }]
}
const index = result.findIndex(item2 => item2.question_type === item.question_type)
if (index !== -1) {
result[index].children.push(item)
} else {
result.push(temp)
}
return result
}, [])
} }
}, },
methods: { methods: {
handleClick(index) { genClass(item) {
this.currentIndex = index return {
this.$emit('indexClick', `${index + 1}`) 'is-error': !item.score
}
},
handleClick(item) {
const qItemEl = document.querySelector(`#question_${item.id}`)
const top = qItemEl.getBoundingClientRect().top
window.scrollTo(0, top + document.documentElement.scrollTop - 84)
} }
} }
} }
...@@ -44,6 +74,9 @@ export default { ...@@ -44,6 +74,9 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.question-num { .question-num {
display: flex;
flex-direction: column;
max-height: calc(100vh - 168px);
ul { ul {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
...@@ -58,18 +91,28 @@ export default { ...@@ -58,18 +91,28 @@ export default {
color: #666; color: #666;
cursor: pointer; cursor: pointer;
margin: 10px 5px; margin: 10px 5px;
} &:hover {
.dotItem { background-color: #ccc;
background-color: rgb(243, 190, 190); }
&.is-error {
border-color: var(--main-color);
}
} }
} }
} }
.question-num-hd {
margin-bottom: 20px;
border-bottom: 1px solid #eee;
}
.question-num-bd { .question-num-bd {
flex: 1;
overflow-y: auto; overflow-y: auto;
max-height: calc(100vh - 220px);
} }
.question-num-ft { .question-num-ft {
margin-top: 20px; position: sticky;
bottom: 0;
background-color: #fff;
padding: 20px 0;
text-align: center; text-align: center;
} }
</style> </style>
<template> <template>
<app-list v-bind="tableOptions" ref="list" v-on="$listeners"> <div>
<template v-slot:filter-permission="{ params }"> <div style="margin-bottom: 25px">
<el-radio-group v-model="params.permission" @change="refetchList"> <span style="font-size: 14px; color: #606266; padding-right: 12px">题库范围:</span>
<el-radio label="1">我的题库</el-radio> <el-radio @change="refetchList" v-model="permission" :label="1">我的题库</el-radio>
<el-radio label="2">公共题库</el-radio> <el-radio @change="refetchList" v-model="permission" :label="2">公共题库</el-radio>
</el-radio-group> </div>
</template> <app-list v-bind="tableOptions" ref="list" v-on="$listeners">
<template v-slot:filter-category="{ params }"> <template v-slot:filter-category="{ params }">
<question-type-treeselect v-model="params.question_category" @change="refetchList"></question-type-treeselect> <question-type-treeselect v-model="params.question_category" @change="refetchList"></question-type-treeselect>
</template> </template>
</app-list> </app-list>
</div>
</template> </template>
<script> <script>
...@@ -20,16 +21,19 @@ export default { ...@@ -20,16 +21,19 @@ export default {
props: { data: { type: Object, default: () => ({}) } }, props: { data: { type: Object, default: () => ({}) } },
components: { QuestionTypeTreeselect }, components: { QuestionTypeTreeselect },
data() { data() {
return {} return {
permission: 1
}
}, },
computed: { computed: {
tableOptions() { tableOptions() {
return { return {
limit: 10, limit: 10,
remote: { remote: {
httpRequest: getQuestionList, httpRequest: getQuestionList,
params: { params: {
permission: '1', permission: this.permission,
question_type: '', question_type: '',
question_title: '', question_title: '',
question_difficulty: '', question_difficulty: '',
...@@ -37,11 +41,11 @@ export default { ...@@ -37,11 +41,11 @@ export default {
} }
}, },
filters: [ filters: [
{ // {
prop: 'permission', // prop: 'permission',
label: '题库范围', // label: '题库范围',
slots: 'filter-permission' // slots: 'filter-permission'
}, // },
{ {
type: 'select', type: 'select',
prop: 'question_type', prop: 'question_type',
...@@ -84,13 +88,13 @@ export default { ...@@ -84,13 +88,13 @@ export default {
prop: 'question_content', prop: 'question_content',
label: '题干内容', label: '题干内容',
placeholder: '请输入题干内容' placeholder: '请输入题干内容'
},
{
type: 'input',
prop: 'question_tag',
label: '知识点',
placeholder: '请输入知识点'
} }
// {
// type: 'input',
// prop: 'question_tag',
// label: '知识点',
// placeholder: '请输入知识点'
// }
], ],
columns: [ columns: [
{ type: 'selection' }, { type: 'selection' },
...@@ -105,7 +109,7 @@ export default { ...@@ -105,7 +109,7 @@ export default {
}, },
{ label: '试卷分类', prop: 'question_category.category_name' }, { label: '试卷分类', prop: 'question_category.category_name' },
{ label: '题目标题', prop: 'question_title' }, { label: '题目标题', prop: 'question_title' },
{ label: '知识点', prop: 'knowledge_point.title' }, // { label: '知识点', prop: 'knowledge_point.title' },
{ {
label: '难度等级', label: '难度等级',
prop: 'question_difficulty', prop: 'question_difficulty',
......
...@@ -14,11 +14,13 @@ ...@@ -14,11 +14,13 @@
<el-descriptions-item label="及格分数">{{ detail.pass_score }}</el-descriptions-item> <el-descriptions-item label="及格分数">{{ detail.pass_score }}</el-descriptions-item>
<el-descriptions-item label="考试时长">{{ detail.paper_times }}分钟</el-descriptions-item> <el-descriptions-item label="考试时长">{{ detail.paper_times }}分钟</el-descriptions-item>
<el-descriptions-item label="最短交卷时长">{{ detail.minimum_paper_handing_time }}分钟</el-descriptions-item> <el-descriptions-item label="最短交卷时长">{{ detail.minimum_paper_handing_time }}分钟</el-descriptions-item>
<el-descriptions-item label="试题顺序">{{ detail.paper_question_order_name }}</el-descriptions-item> <el-descriptions-item label="试题顺序" v-if="detail.paper_type === 1">
{{ detail.paper_question_order_name }}
</el-descriptions-item>
<el-descriptions-item label="多次考试">{{ detail.is_multiple_exams_name }}</el-descriptions-item> <el-descriptions-item label="多次考试">{{ detail.is_multiple_exams_name }}</el-descriptions-item>
<el-descriptions-item label="多次考试成绩计算规则">{{ <el-descriptions-item label="多次考试成绩计算规则" v-if="detail.is_multiple_exams === 1">
detail.multiple_test_score_rule_name {{ detail.multiple_test_score_rule_name }}
}}</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</app-card> </app-card>
<!-- 试卷列表 --> <!-- 试卷列表 -->
......
...@@ -81,8 +81,8 @@ export default { ...@@ -81,8 +81,8 @@ export default {
} }
], ],
columns: [ columns: [
{ type: 'selection', width: '50px', fixed: 'left' }, { type: 'selection', fixed: 'left' },
{ type: 'index', label: '序号', minWidth: '50px', fixed: 'left' }, { type: 'index', label: '序号', fixed: 'left' },
{ {
label: '组卷模式', label: '组卷模式',
prop: 'paper_type', prop: 'paper_type',
...@@ -91,8 +91,16 @@ export default { ...@@ -91,8 +91,16 @@ export default {
return map[row.paper_type] || row.paper_type return map[row.paper_type] || row.paper_type
} }
}, },
{ label: '试卷分类', prop: 'paper_category.category_name' }, {
{ label: '试卷名称', prop: 'paper_title' }, label: '试卷用途',
prop: 'paper_uses',
computed: ({ row }) => {
const map = { 1: '考试', 2: '课后作业', 3: '课程测试' }
return map[row.paper_type] || row.paper_type
}
},
{ label: '试卷分类', prop: 'paper_category.category_name', minWidth: 200 },
{ label: '试卷名称', prop: 'paper_title', minWidth: 200 },
{ label: '总分', prop: 'paper_total_score' }, { label: '总分', prop: 'paper_total_score' },
{ label: '及格分数', prop: 'pass_score' }, { label: '及格分数', prop: 'pass_score' },
{ label: '更新人', prop: 'operator.realname' }, { label: '更新人', prop: 'operator.realname' },
......
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
:controls="false" :controls="false"
v-model="form.pass_score" v-model="form.pass_score"
:min="0" :min="0"
:max="200" :max="form.paper_total_score || 200"
placeholder="请输入及格分数" placeholder="请输入及格分数"
style="width: 100%" style="width: 100%"
:precision="0" :precision="0"
...@@ -140,6 +140,13 @@ export default { ...@@ -140,6 +140,13 @@ export default {
props: { id: { type: String } }, props: { id: { type: String } },
components: { QuestionTypeTreeselect }, components: { QuestionTypeTreeselect },
data() { data() {
const checkPassScore = (rule, value, callback) => {
if (value > this.form.paper_total_score) {
callback(new Error('及格分数不能大于试卷总分'))
} else {
callback()
}
}
return { return {
form: { form: {
paper_title: '', // 试卷名称 paper_title: '', // 试卷名称
...@@ -162,7 +169,10 @@ export default { ...@@ -162,7 +169,10 @@ export default {
paper_type: [{ message: '请选择组卷模式', required: true, trigger: 'change' }], paper_type: [{ message: '请选择组卷模式', required: true, trigger: 'change' }],
paper_total_score: [{ message: '请输入试卷总分', required: true, trigger: 'blur' }], paper_total_score: [{ message: '请输入试卷总分', required: true, trigger: 'blur' }],
paper_question_order: [{ message: '请选择试题顺序', required: true, trigger: 'change' }], paper_question_order: [{ message: '请选择试题顺序', required: true, trigger: 'change' }],
pass_score: [{ message: '请输入及格分数', required: true, trigger: 'blur' }], pass_score: [
{ message: '请输入及格分数', required: true, trigger: 'blur' },
{ validator: checkPassScore, trigger: 'blur' }
],
paper_times: [{ message: '请输入考试时长', required: true, trigger: 'blur' }], paper_times: [{ message: '请输入考试时长', required: true, trigger: 'blur' }],
minimum_paper_handing_time: [{ message: '请输入考试时长', required: true, trigger: 'blur' }], minimum_paper_handing_time: [{ message: '请输入考试时长', required: true, trigger: 'blur' }],
is_multiple_exams: [{ message: '请选择是否多次考试', required: true, trigger: 'change' }] is_multiple_exams: [{ message: '请选择是否多次考试', required: true, trigger: 'change' }]
......
...@@ -49,30 +49,24 @@ httpRequest.interceptors.request.use( ...@@ -49,30 +49,24 @@ httpRequest.interceptors.request.use(
httpRequest.interceptors.response.use( httpRequest.interceptors.response.use(
function (response) { function (response) {
const { data } = response const { data } = response
// 正常返回 if (data.code) {
if (data.code === 0 || data.type === 'application/vnd.ms-excel') { // 未登录
return data if (data.code === 4001) {
} window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}`
// 批阅试卷接口 }
if (data.success && data.message === 'ok') { // 没有权限
return data if (data.code === 4008) {
} router.push('/401')
// 未登录 }
if (data.code === 4001) { // 批阅试卷接口
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}` if (data.code === 403) {
} Message({ message: '没有操作权限', type: 'error' })
// 没有权限 return data
if (data.code === 4008) { }
router.push('/401') Message({ message: data.message, type: 'error' })
} return Promise.reject(data)
// 批阅试卷接口
if (data.code === 403) {
Message({ message: '没有操作权限', type: 'error' })
return data
} }
console.log(data) return data
Message({ message: data.message, type: 'error' })
return Promise.reject(data)
}, },
function (error) { function (error) {
if (error.response) { if (error.response) {
......
import store from '@/store' import store from '@/store'
export default async function (to, from, next) { export default async function (to, from, next) {
// 设置project_tag
const projectTag = to.query.project_tag || ''
if (projectTag) {
store.commit('setActiveProjedct', { tag: projectTag })
}
// 登录白名单 // 登录白名单
const whiteList = [] const whiteList = []
if (whiteList.includes(to.path)) { if (whiteList.includes(to.path)) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论