| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507 |
- <template>
- <ele-modal
- :visible.sync="visible"
- :title="dialogTitle"
- width="80vw"
- append-to-body
- :close-on-click-modal="false"
- :maxable="true"
- @close="cancel"
- >
- <!-- 题库头部信息 -->
- <div class="bank-header">
- <el-form
- ref="bankForm"
- :model="bankForm"
- :rules="bankRules"
- inline
- label-width="120px"
- class="bank-form"
- >
- <el-form-item label="题库名称:" prop="name">
- <el-input
- v-model="bankForm.name"
- placeholder="请输入题库名称"
- :disabled="isView"
- />
- </el-form-item>
- <el-form-item label="所属部门:" prop="deptId">
- <dept-select
- v-model="bankForm.deptId"
- placeholder="请选择所属部门"
- :disabled="isView"
- @changeGroup="onDeptChange"
- />
- </el-form-item>
- </el-form>
- </div>
- <div class="maintenance-body">
- <!-- 左侧题号列表 -->
- <div class="left-panel">
- <div class="panel-title">题号</div>
- <div class="panel-search">
- <el-input
- v-model="questionSearch"
- placeholder="输入关键词搜索试题"
- size="small"
- >
- <el-button
- slot="append"
- type="primary"
- size="small"
- @click="handleSearchQuestion"
- >搜索</el-button
- >
- </el-input>
- </div>
- <el-tree
- ref="treeRef"
- :data="questionList"
- node-key="id"
- default-expand-all
- highlight-current
- :props="{ label: 'label', children: 'children' }"
- @node-click="handleQuestionClick"
- >
- <span class="question-node" slot-scope="{ node, data }">
- <span>{{ node.label }}</span>
- <el-tooltip
- v-if="!isView && data.question"
- content="删除试题"
- placement="top"
- >
- <el-button
- type="text"
- class="question-delete"
- icon="el-icon-delete"
- aria-label="删除试题"
- @click.stop="handleDeleteQuestion(data)"
- />
- </el-tooltip>
- </span>
- </el-tree>
- </div>
- <!-- 右侧试题表单 -->
- <div class="right-panel">
- <question-form ref="questionFormRef" v-model="form" :is-view="isView" />
- </div>
- </div>
- <template v-slot:footer>
- <el-button v-if="!isView" @click="handleReset">重置</el-button>
- <el-button
- v-if="!isView"
- type="primary"
- @click="saveNext"
- :loading="loading"
- >保存并进入下一题</el-button
- >
- <el-button v-else @click="cancel">关闭</el-button>
- </template>
- </ele-modal>
- </template>
- <script>
- import DeptSelect from '@/components/CommomSelect/dept-select.vue';
- import QuestionForm from '../paperManagement/question-form.vue';
- import {
- deleteQuestion,
- edit as updateQuestionBank,
- getById,
- getList,
- save as saveQuestionBank
- } from '@/api/questionBank/questionBank.js';
- const questionTypeMap = {
- 1: 'SINGLE',
- 2: 'MULTIPLE',
- 3: 'JUDGE',
- 4: 'QUESTION_ANSWER'
- };
- const questionTypeReverseMap = {
- SINGLE: '1',
- MULTIPLE: '2',
- JUDGE: '3',
- QUESTION_ANSWER: '4'
- };
- const defBankForm = {
- id: '',
- name: '',
- deptId: '',
- deptName: ''
- };
- const defQuestionForm = {
- id: '',
- sort: '',
- content: '',
- type: '1',
- category: '',
- score: '',
- options: [
- { key: 'A', content: '' },
- { key: 'B', content: '' },
- { key: 'C', content: '' },
- { key: 'D', content: '' }
- ],
- rightAnswer: '',
- rightAnswers: [],
- analysis: ''
- };
- const createQuestionList = () => [];
- export default {
- components: {
- DeptSelect,
- QuestionForm
- },
- data() {
- return {
- visible: false,
- loading: false,
- dialogType: 'add',
- questionSearch: '',
- bankForm: JSON.parse(JSON.stringify(defBankForm)),
- bankRules: {
- name: [
- { required: true, message: '请输入题库名称', trigger: 'blur' }
- ],
- deptId: [
- { required: true, message: '请选择所属部门', trigger: 'change' }
- ]
- },
- form: JSON.parse(JSON.stringify(defQuestionForm)),
- questionList: createQuestionList()
- };
- },
- computed: {
- dialogTitle() {
- const titleMap = {
- add: '维护试题',
- edit: '维护试题',
- view: '查看试题'
- };
- return titleMap[this.dialogType] || '维护试题';
- },
- isView() {
- return this.dialogType === 'view';
- }
- },
- methods: {
- async open(row, type = 'add') {
- this.visible = true;
- this.dialogType = type;
- const questionBankId = row && (row.id || row.questionBankId);
- if (questionBankId) {
- this.bankForm = {
- id: questionBankId,
- name: row.questionBankName || row.name || '',
- deptId: row.departmentId || row.deptId || '',
- deptName: row.departmentName || row.deptName || ''
- };
- this.loading = true;
- try {
- await this.loadDetail(questionBankId, type === 'view');
- } catch (error) {
- this.$message.error(error.message || '获取题库详情失败');
- this.cancel();
- } finally {
- this.loading = false;
- }
- } else {
- this.bankForm = JSON.parse(JSON.stringify(defBankForm));
- this.questionList = createQuestionList();
- this.form = JSON.parse(JSON.stringify(defQuestionForm));
- this.$nextTick(() => {
- this.$refs.treeRef && this.$refs.treeRef.setCurrentKey('1');
- });
- }
- this.$nextTick(() => {
- this.$refs.bankForm && this.$refs.bankForm.clearValidate();
- this.$refs.questionFormRef &&
- this.$refs.questionFormRef.clearValidate();
- });
- },
- cancel() {
- this.bankForm = JSON.parse(JSON.stringify(defBankForm));
- this.form = JSON.parse(JSON.stringify(defQuestionForm));
- this.questionList = createQuestionList();
- this.visible = false;
- },
- onDeptChange(deptId, node) {
- this.bankForm.deptName = node ? node.name : '';
- },
- handleSearchQuestion() {
- // TODO: 搜索试题
- console.log('搜索试题', this.questionSearch);
- },
- handleQuestionClick(data) {
- if (!data.question) return;
- this.form = this.mapQuestionToForm(data.question);
- this.$nextTick(() => {
- this.$refs.questionFormRef &&
- this.$refs.questionFormRef.clearValidate();
- });
- },
- async handleDeleteQuestion(data) {
- try {
- await this.$confirm('确认删除该试题吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- });
- const message = await deleteQuestion(data.id);
- await this.loadDetail(this.bankForm.id, false);
- this.$message.success(message || '删除成功');
- this.$emit('reload');
- } catch (error) {
- if (error !== 'cancel' && error !== 'close') {
- this.$message.error(error.message || '删除失败');
- }
- }
- },
- mapQuestionToForm(question) {
- const type = questionTypeReverseMap[question.questionType] || '1';
- const options = [...(question.optionList || [])]
- .sort((a, b) => Number(a.sortNo) - Number(b.sortNo))
- .map((option, index) => ({
- key: String.fromCharCode(65 + index),
- content: option.optionContent || ''
- }));
- const correctAnswer = question.correctAnswer || '';
- return {
- id: question.id || '',
- sort: question.sortNo === undefined ? '' : String(question.sortNo),
- content: question.questionContent || '',
- type,
- category:
- question.professionalType === undefined
- ? ''
- : String(question.professionalType),
- score:
- question.questionScore === undefined
- ? ''
- : String(question.questionScore),
- options,
- rightAnswer: type === '2' ? '' : correctAnswer,
- rightAnswers:
- type === '2' ? correctAnswer.split(',').filter((item) => item) : [],
- analysis: question.questionExplanation || ''
- };
- },
- async loadDetail(questionBankId, selectFirstQuestion = true) {
- const detail = await getById(questionBankId);
- this.bankForm = {
- id: detail.id || questionBankId,
- name: detail.questionBankName || '',
- deptId: detail.departmentId || '',
- deptName: detail.departmentName || ''
- };
- const questions = [...(detail.questionList || [])].sort(
- (a, b) => Number(a.sortNo) - Number(b.sortNo)
- );
- this.questionList = questions.map((question, index) => ({
- id: question.id,
- label: `第${question.sortNo || index + 1}题`,
- question
- }));
- this.form =
- selectFirstQuestion && questions.length
- ? this.mapQuestionToForm(questions[0])
- : JSON.parse(JSON.stringify(defQuestionForm));
- this.$nextTick(() => {
- if (selectFirstQuestion && questions.length && this.$refs.treeRef) {
- this.$refs.treeRef.setCurrentKey(questions[0].id);
- } else if (this.$refs.treeRef) {
- this.$refs.treeRef.setCurrentKey(null);
- }
- });
- },
- handleReset() {
- this.form = JSON.parse(JSON.stringify(defQuestionForm));
- this.$refs.questionFormRef &&
- this.$refs.questionFormRef.clearValidate();
- },
- toNumber(value) {
- return value === '' || value === null || value === undefined
- ? undefined
- : Number(value);
- },
- buildSavePayload() {
- const isMultiple = this.form.type === '2';
- return {
- questionBankId: this.bankForm.id || undefined,
- questionBankName: this.bankForm.name,
- departmentId: this.bankForm.deptId,
- departmentName: this.bankForm.deptName,
- question: {
- questionId: this.form.id || undefined,
- sortNo: this.toNumber(this.form.sort),
- questionContent: this.form.content,
- questionType: questionTypeMap[this.form.type],
- professionalType: this.toNumber(this.form.category),
- questionScore: this.toNumber(this.form.score),
- correctAnswer: isMultiple
- ? this.form.rightAnswers.join(',')
- : this.form.rightAnswer,
- questionExplanation: this.form.analysis,
- optionList: this.form.options.map((item, index) => ({
- optionContent: item.content,
- sortNo: index + 1
- }))
- }
- };
- },
- async resolveQuestionBankId(saveResult) {
- if (
- typeof saveResult === 'number' ||
- (typeof saveResult === 'string' && /^\d+$/.test(saveResult))
- ) {
- return saveResult;
- }
- const resultId =
- typeof saveResult === 'object'
- ? saveResult.id || saveResult.questionBankId
- : undefined;
- if (resultId) {
- return resultId;
- }
- const result = await getList({
- questionBankName: this.bankForm.name,
- departmentId: this.bankForm.deptId,
- pageNum: 1,
- size: 20
- });
- const questionBank = (result.list || []).find(
- (item) =>
- item.questionBankName === this.bankForm.name &&
- String(item.departmentId) === String(this.bankForm.deptId)
- );
- if (!questionBank) {
- throw new Error('题库已保存,但未能获取题库ID');
- }
- return questionBank.id;
- },
- saveNext() {
- this.$refs.bankForm.validate((bankValid) => {
- if (!bankValid) return;
- this.$refs.questionFormRef.validate((valid) => {
- if (valid) {
- this.loading = true;
- const payload = this.buildSavePayload();
- const saveRequest = this.bankForm.id
- ? updateQuestionBank
- : saveQuestionBank;
- saveRequest(payload)
- .then(async (saveResult) => {
- if (!this.bankForm.id) {
- this.bankForm.id = await this.resolveQuestionBankId(
- saveResult
- );
- }
- await this.loadDetail(this.bankForm.id, false);
- this.$message.success('保存成功');
- this.$emit('reload');
- this.$nextTick(() => {
- this.$refs.questionFormRef &&
- this.$refs.questionFormRef.clearValidate();
- });
- })
- .catch((error) => {
- this.$message.error(error.message || '保存失败');
- })
- .finally(() => {
- this.loading = false;
- });
- }
- });
- });
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .bank-header {
- padding: 10px 15px 0;
- background-color: #f5f7fa;
- border-radius: 4px;
- margin-bottom: 15px;
- .bank-form {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- }
- ::v-deep .el-form-item {
- margin-bottom: 10px;
- }
- }
- .maintenance-body {
- display: flex;
- max-height: calc(90vh - 260px);
- min-height: 450px;
- }
- .left-panel {
- width: 260px;
- flex-shrink: 0;
- border-right: 1px solid #ebeef5;
- padding-right: 15px;
- margin-right: 15px;
- .panel-title {
- font-size: 14px;
- font-weight: bold;
- text-align: center;
- padding: 8px 0;
- background-color: #409eff;
- color: #fff;
- border-radius: 4px 4px 0 0;
- }
- .panel-search {
- padding: 10px 0;
- ::v-deep .el-input-group__append {
- background-color: #409eff;
- color: #fff;
- border-color: #409eff;
- }
- }
- .question-node {
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex: 1;
- padding-right: 8px;
- }
- .question-delete {
- color: #f56c6c;
- }
- }
- .right-panel {
- flex: 1;
- overflow-y: auto;
- padding-right: 10px;
- }
- .question-form {
- padding-top: 10px;
- }
- .danger-text {
- color: #f56c6c;
- }
- </style>
|