浏览代码

阅读理解/听力/完型填空

zhangjie 2 年之前
父节点
当前提交
bf50d6532b

+ 45 - 0
src/assets/styles/pages.scss

@@ -874,3 +874,48 @@
     display: none;
   }
 }
+
+.nested-question {
+  .subq-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    flex-grow: 2;
+    padding-right: 10px;
+  }
+  .el-collapse {
+    margin: 10px 0;
+    border: none;
+  }
+  .el-collapse-item {
+    margin: 5px 0;
+  }
+  .el-collapse-item__header {
+    height: 30px;
+    padding: 5px 10px;
+    line-height: 20px;
+    background-color: $--color-background;
+  }
+  .el-collapse-item__wrap {
+    padding: 10px 0;
+    border: none;
+  }
+}
+.nested-subq-popover {
+  padding: 0;
+  .nested-subq {
+    &-item {
+      padding: 10px;
+      line-height: 20px;
+      text-align: center;
+      cursor: pointer;
+      &:not(:first-child) {
+        border-top: 1px solid $--color-background;
+      }
+      &:hover {
+        color: $--color-primary;
+        background-color: mix(#fff, $--color-primary, 80%);
+      }
+    }
+  }
+}

+ 7 - 0
src/constants/constants.js

@@ -147,6 +147,13 @@ export const QUESTION_TYPES = [
   { code: "PARAGRAPH_MATCHING", name: "段落匹配" },
   { code: "BANKED_CLOZE", name: "选词填空" },
 ];
+export const BASE_QUESTION_TYPES = [
+  { code: "SINGLE_ANSWER_QUESTION", name: "单选" },
+  { code: "MULTIPLE_ANSWER_QUESTION", name: "多选" },
+  { code: "BOOL_ANSWER_QUESTION", name: "判断" },
+  { code: "FILL_BLANK_QUESTION", name: "填空" },
+  { code: "TEXT_ANSWER_QUESTION", name: "问答" },
+];
 
 export const DIFFICULTY_LIST = [
   { code: "难", name: "难" },

+ 11 - 16
src/modules/question/components/QuestionEditDialog.vue

@@ -59,27 +59,19 @@
 
 <script>
 import { QUESTION_TYPES } from "@/constants/constants";
-import { getInitQuestionModel } from "./model/questionModel";
+import {
+  getInitQuestionModel,
+  STRUCT_TYPE_COMP_DICT,
+} from "./model/questionModel";
 import BooleanQuestion from "./edit/BooleanQuestion.vue";
 import FillBlankQuestion from "./edit/FillBlankQuestion.vue";
 import SelectQuestion from "./edit/SelectQuestion.vue";
 import TextAnswerQuestion from "./edit/TextAnswerQuestion.vue";
+import NestedQuestion from "./edit/NestedQuestion.vue";
+import BankedClozeQuestion from "./edit/BankedClozeQuestion.vue";
 import { randomCode } from "@/plugins/utils";
 import { updateQuestionApi } from "../api";
 
-const structTypeCompDict = {
-  SINGLE_ANSWER_QUESTION: "select-question",
-  MULTIPLE_ANSWER_QUESTION: "select-question",
-  BOOL_ANSWER_QUESTION: "boolean-question",
-  FILL_BLANK_QUESTION: "fill-blank-question",
-  TEXT_ANSWER_QUESTION: "text-answer-question",
-  READING_COMPREHENSION: "",
-  LISTENING_QUESTION: "",
-  CLOZE: "",
-  PARAGRAPH_MATCHING: "",
-  BANKED_CLOZE: "",
-};
-
 export default {
   name: "QuestionEditDialog",
   components: {
@@ -87,6 +79,8 @@ export default {
     SelectQuestion,
     TextAnswerQuestion,
     BooleanQuestion,
+    NestedQuestion,
+    BankedClozeQuestion,
   },
   props: {
     question: {
@@ -100,7 +94,8 @@ export default {
     return {
       modalIsShow: false,
       QUESTION_TYPES,
-      curQuestionType: "SINGLE_ANSWER_QUESTION",
+      curQuestionType: "READING_COMPREHENSION",
+      // curQuestionType: "SINGLE_ANSWER_QUESTION",
       questionModel: {},
       questionKey: "",
       loading: false,
@@ -114,7 +109,7 @@ export default {
       return this.isEdit ? "编辑试题" : "创建试题";
     },
     structTypeComp() {
-      return structTypeCompDict[this.curQuestionType];
+      return STRUCT_TYPE_COMP_DICT[this.curQuestionType];
     },
   },
   methods: {

+ 13 - 0
src/modules/question/components/edit/BankedClozeQuestion.vue

@@ -0,0 +1,13 @@
+<template>
+  <div class="banked-cloze-question">BankedClozeQuestion</div>
+</template>
+
+<script>
+export default {
+  name: "BankedClozeQuestion",
+  data() {
+    return {};
+  },
+  methods: {},
+};
+</script>

+ 4 - 0
src/modules/question/components/edit/FillBlankQuestion.vue

@@ -11,6 +11,7 @@
           v-model="modalForm.quesBody"
           enable-answer-point
           @answer-point-changed="answerPointsChange"
+          @change="quesBodyChange"
         ></v-editor>
       </el-form-item>
       <el-form-item label="答案"></el-form-item>
@@ -115,6 +116,9 @@ export default {
         }
       }
     },
+    quesBodyChange() {
+      this.$refs.modalFormComp.validateField(`quesBody`, () => {});
+    },
     questionInfoChange(questionInfo) {
       this.modalForm = Object.assign({}, this.modalForm, questionInfo);
     },

+ 215 - 0
src/modules/question/components/edit/NestedQuestion.vue

@@ -0,0 +1,215 @@
+<template>
+  <div class="nested-question">
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      label-width="100px"
+    >
+      <el-form-item prop="quesBody" label="大题题干">
+        <v-editor
+          v-model="modalForm.quesBody"
+          :enable-answer-point="IS_CLOZE"
+          @answer-point-changed="answerPointsChange"
+          @change="quesBodyChange"
+        ></v-editor> </el-form-item
+    ></el-form>
+
+    <div class="tips-info">
+      <p>小题数:{{ modalForm.subQuestions.length }}</p>
+    </div>
+
+    <el-collapse v-model="activeNames">
+      <el-collapse-item
+        v-for="(subq, index) in modalForm.subQuestions"
+        :key="index"
+        :name="index"
+      >
+        <div slot="title" class="subq-header">
+          <h3>
+            第{{ index + 1 }}小题({{ subq.questionType | questionType }})
+          </h3>
+          <div>
+            <el-button
+              v-if="!deleleDisabled"
+              size="mini"
+              type="text"
+              icon="el-icon-delete"
+              title="删除"
+              @click.stop="removeSubQuestion(index)"
+            ></el-button>
+          </div>
+        </div>
+
+        <component
+          :is="getStructTypeComp(subq.questionType)"
+          :key="subq.questionKey"
+          ref="QuestionEditDetail"
+          :question="subq"
+        ></component>
+      </el-collapse-item>
+    </el-collapse>
+
+    <el-popover
+      v-if="!IS_CLOZE"
+      popper-class="nested-subq-popover"
+      trigger="hover"
+    >
+      <div class="nested-subq-list">
+        <div
+          v-for="item in BASE_QUESTION_TYPES"
+          :key="item.code"
+          class="nested-subq-item"
+          @click="addSubQuestion(item.code)"
+        >
+          {{ item.name }}
+        </div>
+      </div>
+
+      <el-button
+        slot="reference"
+        type="primary"
+        size="small"
+        icon="el-icon-circle-plus-outline"
+        >新增小题</el-button
+      >
+    </el-popover>
+  </div>
+</template>
+
+<script>
+import { isAnEmptyRichText } from "@/utils/utils";
+import { randomCode } from "@/plugins/utils";
+import {
+  getInitQuestionModel,
+  STRUCT_TYPE_COMP_DICT,
+} from "../model/questionModel";
+import BooleanQuestion from "./BooleanQuestion.vue";
+import FillBlankQuestion from "./FillBlankQuestion.vue";
+import SelectQuestion from "./SelectQuestion.vue";
+import TextAnswerQuestion from "./TextAnswerQuestion.vue";
+import { BASE_QUESTION_TYPES } from "@/constants/constants";
+
+export default {
+  name: "NestedQuestion",
+  components: {
+    FillBlankQuestion,
+    SelectQuestion,
+    TextAnswerQuestion,
+    BooleanQuestion,
+  },
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalForm: {},
+      BASE_QUESTION_TYPES,
+      activeNames: [],
+      rules: {
+        quesBody: [
+          {
+            validator: (rule, value, callback) => {
+              if (!this.IS_CLOZE) return callback();
+
+              if (!value || isAnEmptyRichText(value)) {
+                return callback(new Error(`请输入题干`));
+              }
+              callback();
+            },
+            trigger: "change",
+          },
+        ],
+      },
+    };
+  },
+  computed: {
+    deleleDisabled() {
+      return this.IS_CLOZE;
+    },
+    IS_CLOZE() {
+      return this.modalForm.questionType === "CLOZE";
+    },
+  },
+  created() {
+    this.initData();
+  },
+  methods: {
+    initData() {
+      this.modalForm = this.$objAssign(
+        getInitQuestionModel("READING_COMPREHENSION"),
+        this.question
+      );
+    },
+    getStructTypeComp(questionType) {
+      return STRUCT_TYPE_COMP_DICT[questionType];
+    },
+    addSubQuestion(questionType, index = null) {
+      let newQuestion = getInitQuestionModel(questionType);
+      newQuestion.questionKey = randomCode();
+      const nindex =
+        index === null ? this.modalForm.subQuestions.length : index + 1;
+      this.modalForm.subQuestions.splice(nindex, 0, newQuestion);
+      if (!this.activeNames.includes(nindex) && !this.IS_CLOZE)
+        this.activeNames.push(nindex);
+    },
+    async removeSubQuestion(index) {
+      if (this.deleleDisabled) return;
+
+      const res = await this.$confirm("确定要删除该小题吗?", "提示", {
+        type: "warning",
+      }).catch(() => {});
+      if (res !== "confirm") return;
+
+      this.modalForm.subQuestions.splice(index, 1);
+    },
+    answerPointsChange(answerPointsCount) {
+      let curSubQuestionCount = this.modalForm.subQuestions.length;
+      if (curSubQuestionCount === answerPointsCount) return;
+
+      if (curSubQuestionCount > answerPointsCount) {
+        this.modalForm.subQuestions = this.modalForm.subQuestions.slice(
+          0,
+          answerPointsCount
+        );
+      } else {
+        for (let i = curSubQuestionCount; i < answerPointsCount; i++) {
+          this.addSubQuestion("SINGLE_ANSWER_QUESTION");
+        }
+      }
+    },
+    quesBodyChange() {
+      this.$refs.modalFormComp.validateField(`quesBody`, () => {});
+    },
+    async validate() {
+      let errorQuestionIndexs = [];
+      for (let i = 0; i < this.$refs.QuestionEditDetail.length; i++) {
+        const compInst = this.$refs.QuestionEditDetail[i];
+        const res = await compInst.validate().catch(() => {});
+        if (!res) {
+          errorQuestionIndexs.push(i + 1);
+        }
+      }
+      if (errorQuestionIndexs.length) {
+        this.$message.error(
+          `请完成第${errorQuestionIndexs.join()}小题的编辑!`
+        );
+        return Promise.reject();
+      }
+
+      return true;
+    },
+    getData() {
+      this.modalForm.subQuestions = this.$refs.QuestionEditDetail.map(
+        (compInst) => compInst.getData()
+      );
+      return this.modalForm;
+    },
+  },
+};
+</script>

+ 15 - 5
src/modules/question/components/model/questionModel.js

@@ -59,12 +59,9 @@ export const textAnswerQuestion = {
 // 完型填空/听力/阅读理解
 export const readingComprehensionQuestion = {
   questionType: "READING_COMPREHENSION",
-  quesBody: {
-    sections: [],
-  },
-  comment: { sections: [] },
+  quesBody: null,
+  comment: null,
   subQuestions: [],
-  // answer: [{ number: 1, answer: [8] }],
 };
 
 // 段落匹配/选词填空
@@ -126,3 +123,16 @@ export const getInitQuestionModel = (qtype) => {
     ...deepCopy(models[qtype]),
   };
 };
+
+export const STRUCT_TYPE_COMP_DICT = {
+  SINGLE_ANSWER_QUESTION: "select-question",
+  MULTIPLE_ANSWER_QUESTION: "select-question",
+  BOOL_ANSWER_QUESTION: "boolean-question",
+  FILL_BLANK_QUESTION: "fill-blank-question",
+  TEXT_ANSWER_QUESTION: "text-answer-question",
+  READING_COMPREHENSION: "nested-question",
+  LISTENING_QUESTION: "nested-question",
+  CLOZE: "nested-question",
+  PARAGRAPH_MATCHING: "banked-cloze-question",
+  BANKED_CLOZE: "banked-cloze-question",
+};