试卷管理

dev
LiuJiaNan 2024-03-15 17:56:45 +08:00
parent 375021971d
commit ab93425d83
6 changed files with 656 additions and 6 deletions

View File

@ -418,3 +418,7 @@
.el-color-predefine__color-selector.selected { .el-color-predefine__color-selector.selected {
--el-color-primary: #fff !important; --el-color-primary: #fff !important;
} }
.el-input-number.is-disabled .el-input-number__decrease, .el-input-number.is-disabled .el-input-number__increase{
border-color: var(--el-border-color) !important;
}

View File

@ -241,3 +241,10 @@ export const layoutFnGetTrainingLevel = async (params) => {
const resData = await getTrainingLevel(params); const resData = await getTrainingLevel(params);
return ref(resData.varList); return ref(resData.varList);
}; };
// 试题标签
export const layoutFnGetTestQuestionLabels = async () => {
const resData = await getLevels({
DICTIONARIES_ID: "a60ebc858e2c46108bf82bbd8acc8f50",
});
return ref(resData.list);
};

View File

@ -1,4 +1,20 @@
import { post } from "@/request/axios.js"; import { post, upload } from "@/request/axios.js";
export const setExamPaperManagementDelete = (params) => export const setExamPaperManagementDelete = (params) =>
post("/stageexampaperinput/delete", params); // 试卷管理删除 post("/stageexampaperinput/delete", params); // 试卷管理删除
export const setExamPaperManagementAdd = (params) =>
upload("/stageexampaperinput/add", params); // 试卷管理添加
export const setExamPaperManagementEdit = (params) =>
upload("/stageexampaperinput/edit", params); // 试卷管理修改
export const setExamPaperManagementInherit = (params) =>
post("/stageexampaperinput/inherit", params); // 试卷管理继承
export const setExamPaperManagementAddToDraft = (params) =>
upload("/stageexampaperCache/add", params); // 试卷管理保存到草稿
export const setExamPaperManagementTestQuestionsDelete = (params) =>
post("/paperQuestion/delete", params); // 试卷管理试题删除
export const setExamPaperManagementTestQuestionsAdd = (params) =>
post("/paperQuestion/add", params); // 试卷管理试题新增
export const setExamPaperManagementTestQuestionsEdit = (params) =>
post("/paperQuestion/edit", params); // 试卷管理试题修改
export const getAssociatedCoursewareNameList = (params) =>
post("/videocourseware/getCourseWareName", params); // 关联课件名称

View File

@ -1,7 +1,380 @@
<template> <template>
<layout-card>11</layout-card> <layout-card>
<el-divider content-position="left">试卷基本信息</el-divider>
<el-form
ref="formRef"
:model="data.form"
:rules="rules"
label-width="100px"
>
<el-row>
<el-col :span="24">
<el-form-item label="试卷名称" prop="EXAMNAME">
<el-input v-model="data.form.EXAMNAME" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="总分数" prop="EXAMSCORE">
<el-input-number
v-model="data.form.EXAMSCORE"
:disabled="type !== 'add'"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合格分数" prop="PASSSCORE">
<el-input-number v-model="data.form.PASSSCORE" />
</el-form-item>
</el-col>
<template v-if="type === 'add'">
<el-col :span="12">
<el-form-item label="下载模板">
<el-button type="primary" @click="fnDownloadTemplate">
下载
</el-button>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="上传试题" prop="fileList">
<layout-upload
v-model:file-list="data.form.fileList"
accept=".XLS,.XLSX,.xls,.xlsx"
:size="1"
>
<template #tip>
只能上传.XLS .XLSX格式的单个文件且文件大小不超过1MB
</template>
</layout-upload>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<div v-if="type !== 'add'">
<el-divider content-position="left">试卷题目信息</el-divider>
<el-form
:model="searchForm"
label-width="100px"
@submit.prevent="fnGetData"
>
<el-row>
<el-col :span="6">
<el-form-item label="题目内容" prop="KEYWORDS">
<el-input v-model="searchForm.KEYWORDS" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="题目类型" prop="QUESTIONTYPE">
<el-select v-model="searchForm.QUESTIONTYPE">
<el-option
v-for="item in questionTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label-width="10px">
<el-button type="primary" native-type="submit">搜索</el-button>
<el-button native-type="reset" @click="fnGetData">
重置
</el-button>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label-width="10px" class="end">
<el-button type="primary" @click="fnAddOrEdit({}, '')">
新增试题
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="items mt-20 p-20">
<div
v-for="(item, index) in list"
:key="item.PAPER_QUESTION_ID"
class="item ptb-20"
>
<div class="mt-10">
{{ index + 1 }}.
<span v-if="item.QUESTIONTYPE === '1'"> () </span>
<span v-if="item.QUESTIONTYPE === '2'"> () </span>
<span v-if="item.QUESTIONTYPE === '3'"> () </span>
{{ item.QUESTIONDRY }}
<span class="ml-10">(题目分值{{ item.SCORE }})</span>
</div>
<div class="mt-10 ml-30">
<el-radio-group
v-if="item.QUESTIONTYPE === '1'"
disabled
:model-value="item.ANSWER"
>
<el-radio label="A">A.{{ item.OPTIONA }}</el-radio>
<el-radio label="B">B.{{ item.OPTIONB }}</el-radio>
<el-radio label="C">C.{{ item.OPTIONC }}</el-radio>
<el-radio label="D">D.{{ item.OPTIOND }}</el-radio>
</el-radio-group>
<el-checkbox-group
v-if="item.QUESTIONTYPE === '2'"
disabled
:model-value="item.ANSWER?.split('')"
>
<el-checkbox label="A">A.{{ item.OPTIONA }}</el-checkbox>
<el-checkbox label="B">B.{{ item.OPTIONB }}</el-checkbox>
<el-checkbox label="C">C.{{ item.OPTIONC }}</el-checkbox>
<el-checkbox label="D">D.{{ item.OPTIOND }}</el-checkbox>
</el-checkbox-group>
<el-radio-group
v-if="item.QUESTIONTYPE === '3'"
disabled
:model-value="item.ANSWER"
>
<el-radio label="A">A.{{ item.OPTIONA }}</el-radio>
<el-radio label="B">B.{{ item.OPTIONB }}</el-radio>
</el-radio-group>
</div>
<div class="flex">
<div>
<div class="mt-10">答案{{ item.ANSWER }}</div>
<div class="mt-10">答案解析{{ item.DESCR }}</div>
<div class="mt-10">关联课件名称{{ item.COURSEWARENAME }}</div>
</div>
<div class="tr">
<el-button @click="fnAddOrEdit(item, index)">编辑</el-button>
<el-button @click="fnDelete(item, index)"> 删除 </el-button>
</div>
</div>
</div>
</div>
</div>
<div class="mt-10 tc">
<el-button type="primary" @click="fnSubmit('normal')">
{{ buttonTextMap[type] }}
</el-button>
<el-button type="primary" @click="fnSubmit('draft')">
保存到草稿箱
</el-button>
</div>
<add-test-questions
:id="STAGEEXAMPAPERINPUT_ID"
v-model:visible="data.addOrEditDialog.visible"
v-model:form="data.addOrEditDialog.form"
:type="data.addOrEditDialog.type"
:is-inherit="type === 'inherit'"
@get-data="fnGetDataInfo"
@confirm="fnAddTestQuestionsConfirm"
/>
</layout-card>
</template> </template>
<script setup></script> <script setup>
import { nextTick, reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import LayoutUpload from "@/components/upload/index.vue";
import { useRoute, useRouter } from "vue-router";
import { debounce } from "throttle-debounce";
import useFormValidate from "@/assets/js/useFormValidate.js";
import {
setExamPaperManagementAdd,
setExamPaperManagementAddToDraft,
setExamPaperManagementEdit,
setExamPaperManagementInherit,
setExamPaperManagementTestQuestionsDelete,
} from "@/request/training_process_management.js";
import {
getExamPaperManagementTestQuestions,
getExamPaperManagementView,
} from "@/request/training_resource_management.js";
import useListData from "@/assets/js/useListData.js";
import { cloneDeep, sumBy } from "lodash-es";
import AddTestQuestions from "./components/add_test_questions.vue";
<style scoped lang="scss"></style> const route = useRoute();
const router = useRouter();
const { type, STAGEEXAMPAPERINPUT_ID } = route.query;
const buttonTextMap = {
add: "立即创建",
edit: "保存修改",
inherit: "继承试卷",
};
const questionTypeOptions = [
{ value: "1", label: "单选题" },
{ value: "2", label: "多选题" },
{ value: "3", label: "判断题" },
];
const rules = {
EXAMNAME: [{ required: true, message: "请输入试卷名称", trigger: "blur" }],
EXAMSCORE: [{ required: true, message: "请输入总分数", trigger: "blur" }],
PASSSCORE: [{ required: true, message: "请输入合格分数", trigger: "blur" }],
fileList: [{ required: true, message: "请上传试题", trigger: "change" }],
};
const formRef = ref(null);
const data = reactive({
form: {
EXAMNAME: "",
EXAMSCORE: 0,
PASSSCORE: 0,
fileList: [],
},
addOrEditDialog: {
visible: false,
form: {
QUESTIONTYPE: "",
QUESTIONDRY: "",
OPTIONA: "",
OPTIONB: "",
OPTIONC: "",
OPTIOND: "",
ANSWER: "",
SCORE: 0,
LABEL_TYPE: "",
VIDEOCOURSEWARE_ID: "",
},
type: "",
index: "",
},
});
const { list, searchForm, fnGetData } = useListData(
getExamPaperManagementTestQuestions,
{
otherParams: { STAGEEXAMPAPERINPUT_ID },
immediate: false,
usePagination: false,
}
);
const fnGetDataInfo = async () => {
if (!STAGEEXAMPAPERINPUT_ID) return;
const resData = await getExamPaperManagementView({ STAGEEXAMPAPERINPUT_ID });
resData.pd.EXAMSCORE = +resData.pd.EXAMSCORE;
resData.pd.PASSSCORE = +resData.pd.PASSSCORE;
data.form = resData.pd;
fnGetData();
};
fnGetDataInfo();
const fnDownloadTemplate = async () => {
await ElMessageBox.confirm("确定要下载excel模板吗", {
type: "warning",
});
window.open(
import.meta.env[import.meta.env.DEV ? "VITE_PROXY" : "VITE_BASE_URL"] +
"question/downExcel"
);
};
const fnAddOrEdit = async (row, index) => {
data.addOrEditDialog.visible = true;
await nextTick();
data.addOrEditDialog.type = row.PAPER_QUESTION_ID ? "edit" : "add";
data.addOrEditDialog.index = index;
if (row.PAPER_QUESTION_ID) {
data.addOrEditDialog.form = cloneDeep(row);
data.addOrEditDialog.form.SCORE = +row.SCORE;
}
};
const fnAddTestQuestionsConfirm = (value) => {
if (data.addOrEditDialog.type === "add") list.value.push(value);
else list.value.splice(data.addOrEditDialog.index, 1, value);
data.form.EXAMSCORE = sumBy(list.value, (item) => +item.SCORE);
};
const fnDelete = debounce(
1000,
async (row, index) => {
await ElMessageBox.confirm("确定要删除吗?", {
type: "warning",
});
if (type === "inherit") {
list.value.splice(index, 1);
data.form.EXAMSCORE = data.form.EXAMSCORE - row.SCORE;
ElMessage.success("删除成功");
} else {
await setExamPaperManagementTestQuestionsDelete({
PAPER_QUESTION_ID: row.PAPER_QUESTION_ID,
STAGEEXAMPAPERINPUT_ID,
});
ElMessage.success("删除成功");
await fnGetDataInfo();
}
},
{ atBegin: true }
);
const fnSubmit = debounce(
1000,
async (submitType) => {
await useFormValidate(formRef);
if (data.form.PASSSCORE > data.form.EXAMSCORE) {
ElMessage.warning("合格分数不能大于总分数");
return;
}
const formData = new FormData();
Object.keys(data.form).forEach((key) => {
formData.append(key, data.form[key]);
});
formData.delete("fileList");
if (data.form.fileList?.length > 0) {
formData.append("FFILENAME", data.form.fileList[0].name);
formData.append("FFILE", data.form.fileList[0].raw);
}
let resData;
if (submitType === "draft") {
resData = await setExamPaperManagementAddToDraft(formData);
} else if (submitType === "normal") {
if (type === "add") {
resData = await setExamPaperManagementAdd(formData);
}
if (type === "edit") {
resData = await setExamPaperManagementEdit(formData);
}
if (type === "inherit") {
await setExamPaperManagementInherit({
...data.form,
queList: JSON.stringify(list.value),
});
router.back();
return;
}
}
if (resData.code === 1) {
ElMessage.success("保存成功");
router.back();
} else {
ElMessage({
dangerouslyUseHTMLString: true,
message: resData.msg,
type: "error",
showClose: true,
duration: 10 * 1000,
});
}
},
{ atBegin: true }
);
</script>
<style scoped lang="scss">
.items {
border: 1px solid var(--el-border-color);
.item {
border-bottom: 1px dashed #ebeef5;
&:first-child {
padding-top: 0;
}
&:last-child {
border-bottom: none;
}
}
}
.flex {
display: flex;
align-items: center;
div {
flex: 1;
}
}
</style>

View File

@ -0,0 +1,250 @@
<template>
<el-dialog
v-model="visible"
:title="type === 'add' ? '新增' : '修改'"
@close="fnClose"
@open="fnQuestionTypeChange"
>
<el-form ref="formRef" :rules="rules" :model="form" label-width="110px">
<el-form-item label="试题类型" prop="QUESTIONTYPE">
<el-select v-model="form.QUESTIONTYPE" @change="fnQuestionTypeChange">
<el-option
v-for="item in questionTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="题干" prop="QUESTIONDRY">
<el-input
v-model="form.QUESTIONDRY"
type="textarea"
:autosize="{ minRows: 3 }"
/>
</el-form-item>
<template v-if="form.QUESTIONTYPE !== '3' && form.QUESTIONTYPE !== '4'">
<el-form-item label="选项A" prop="OPTIONA">
<el-input v-model="form.OPTIONA" />
</el-form-item>
<el-form-item label="选项B" prop="OPTIONB">
<el-input v-model="form.OPTIONB" />
</el-form-item>
<el-form-item label="选项C" prop="OPTIONC">
<el-input v-model="form.OPTIONC" />
</el-form-item>
<el-form-item label="选项D" prop="OPTIOND">
<el-input v-model="form.OPTIOND" />
</el-form-item>
</template>
<el-form-item label="答案" prop="ANSWER">
<el-select v-model="form.ANSWER">
<el-option
v-for="item in answerOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="分值" prop="SCORE">
<el-input-number v-model="form.SCORE" />
</el-form-item>
<el-form-item label="试题标签" prop="LABEL_TYPE">
<el-select v-model="form.LABEL_TYPE">
<el-option
v-for="item in testQuestionLabels"
:key="item.DICTIONARIES_ID"
:label="item.NAME"
:value="item.DICTIONARIES_ID"
/>
</el-select>
</el-form-item>
<el-form-item label="关联课件名称" prop="VIDEOCOURSEWARE_ID">
<el-select v-model="form.VIDEOCOURSEWARE_ID" filterable>
<el-option
v-for="item in associatedCoursewareName"
:key="item.VIDEOCOURSEWARE_ID"
:label="item.COURSEWARENAME"
:value="item.VIDEOCOURSEWARE_ID"
/>
</el-select>
</el-form-item>
<el-form-item label="答案解析" prop="DESCR">
<el-input
v-model="form.DESCR"
type="textarea"
:autosize="{ minRows: 3 }"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="fnClose"></el-button>
<el-button type="primary" @click="fnSubmit"></el-button>
</template>
</el-dialog>
</template>
<script setup>
import { useVModels } from "@vueuse/core";
import { nextTick, ref } from "vue";
import { debounce } from "throttle-debounce";
import useFormValidate from "@/assets/js/useFormValidate.js";
import { ElMessage } from "element-plus";
import { layoutFnGetTestQuestionLabels } from "@/assets/js/data_dictionary.js";
import {
getAssociatedCoursewareNameList,
setExamPaperManagementTestQuestionsAdd,
setExamPaperManagementTestQuestionsEdit,
} from "@/request/training_process_management.js";
import { cloneDeep } from "lodash-es";
const questionTypeOptions = [
{ value: "1", label: "单选题" },
{ value: "2", label: "多选题" },
{ value: "3", label: "判断题" },
];
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
form: {
type: Object,
required: true,
default: () => ({}),
},
type: {
type: String,
required: true,
default: "",
},
id: {
type: String,
required: true,
default: "",
},
isInherit: {
type: Boolean,
required: true,
default: false,
},
});
const emits = defineEmits([
"update:visible",
"update:form",
"get-data",
"confirm",
]);
const { visible, form } = useVModels(props, emits);
const formRef = ref(null);
const answerOptions = ref([]);
const associatedCoursewareName = ref([]);
const rules = {
QUESTIONTYPE: [
{ required: true, message: "请选择试题类型", trigger: "change" },
],
QUESTIONDRY: [{ required: true, message: "请输入题干", trigger: "blur" }],
OPTIONA: [{ required: true, message: "请输入选项A", trigger: "blur" }],
OPTIONB: [{ required: true, message: "请输入选项B", trigger: "blur" }],
OPTIONC: [{ required: true, message: "请输入选项C", trigger: "blur" }],
OPTIOND: [{ required: true, message: "请输入选项D", trigger: "blur" }],
ANSWER: [{ required: true, message: "请选择答案", trigger: "change" }],
SCORE: [{ required: true, message: "请输入分值", trigger: "blur" }],
LABEL_TYPE: [
{ required: true, message: "请选择试题标签", trigger: "change" },
],
VIDEOCOURSEWARE_ID: [
{ required: true, message: "请选择关联课件名称", trigger: "change" },
],
};
const testQuestionLabels = await layoutFnGetTestQuestionLabels();
const fnGetAssociatedCoursewareNameList = async () => {
const resData = await getAssociatedCoursewareNameList();
associatedCoursewareName.value = resData.CourseWareNameList;
};
fnGetAssociatedCoursewareNameList();
const fnQuestionTypeChange = async () => {
await nextTick();
if (form.value.QUESTIONTYPE === "1") {
answerOptions.value = [
{ value: "A", label: "A" },
{ value: "B", label: "B" },
{ value: "C", label: "C" },
{ value: "D", label: "D" },
];
} else if (form.value.QUESTIONTYPE === "2") {
answerOptions.value = [
{ value: "AB", label: "AB" },
{ value: "AC", label: "AC" },
{ value: "AD", label: "AD" },
{ value: "BC", label: "BC" },
{ value: "BD", label: "BD" },
{ value: "CD", label: "CD" },
{ value: "ABC", label: "ABC" },
{ value: "ABD", label: "ABD" },
{ value: "ACD", label: "ACD" },
{ value: "BCD", label: "BCD" },
{ value: "ABCD", label: "ABCD" },
];
} else if (form.value.QUESTIONTYPE === "3") {
answerOptions.value = [
{ value: "A", label: "对" },
{ value: "B", label: "错" },
];
} else {
answerOptions.value = [];
}
};
const fnClose = () => {
formRef.value.resetFields();
visible.value = false;
};
const fnSubmit = debounce(
1000,
async () => {
await useFormValidate(formRef);
if (form.value.QUESTIONTYPE === "3") {
form.value.OPTIONA = "对";
form.value.OPTIONB = "错";
form.value.OPTIONC = "";
form.value.OPTIOND = "";
}
let arr = [];
if (form.value.QUESTIONTYPE === "3") {
arr = ["#" + form.value.OPTIONA + "#", "#" + form.value.OPTIONB + "#"];
} else if (form.value.QUESTIONTYPE !== "4") {
arr = [
"#" + form.value.OPTIONA + "#",
"#" + form.value.OPTIONB + "#",
"#" + form.value.OPTIONC + "#",
"#" + form.value.OPTIOND + "#",
];
}
const s = arr.join(",") + ",";
for (let i = 0; i < arr.length - 1; i++) {
if (s.replace(arr[i] + ",", "").indexOf(arr[i]) > -1) {
ElMessage.warning("试题答案重复:" + arr[i].split("#").join(""));
return;
}
}
if (!props.isInherit) {
props.type === "add"
? await setExamPaperManagementTestQuestionsAdd({
...form.value,
STAGEEXAMPAPERINPUT_ID: props.id,
})
: await setExamPaperManagementTestQuestionsEdit({ ...form.value });
emits("get-data");
} else {
emits("confirm", cloneDeep(form.value));
}
ElMessage.success("操作成功");
fnClose();
},
{ atBegin: true }
);
</script>
<style scoped lang="scss"></style>

View File

@ -93,7 +93,7 @@
查看 查看
</el-button> </el-button>
<el-button <el-button
v-if="row.SOURCETYPE === '1' || row.PAPERUSERCOUNT > 0" v-if="row.SOURCETYPE === '2' || row.PAPERUSERCOUNT === 0"
type="primary" type="primary"
text text
link link
@ -126,7 +126,7 @@
继承 继承
</el-button> </el-button>
<el-button <el-button
v-show="row.SOURCETYPE === '1' || row.PAPERUSERCOUNT > 0" v-if="row.SOURCETYPE === '2' || row.PAPERUSERCOUNT === 0"
type="primary" type="primary"
text text
link link