Przeglądaj źródła

选择题编辑

zhangjie 2 lat temu
rodzic
commit
c2b5c6df09

+ 4 - 0
src/assets/styles/base.scss

@@ -263,6 +263,10 @@ body {
   align-items: center;
   justify-content: space-between;
 }
+.box-flex {
+  display: flex;
+  align-items: center;
+}
 .body-content {
   margin: 15px;
 }

+ 14 - 1
src/constants/constants.js

@@ -134,7 +134,7 @@ export const DIFFICULTY_LEVEL_ENUM = {
   EASY: "易",
 };
 
-// question
+//
 export const QUESTION_TYPES = [
   { code: "SINGLE_ANSWER_QUESTION", name: "单选" },
   { code: "MULTIPLE_ANSWER_QUESTION", name: "多选" },
@@ -147,3 +147,16 @@ export const QUESTION_TYPES = [
   { code: "PARAGRAPH_MATCHING", name: "段落匹配" },
   { code: "BANKED_CLOZE", name: "选词填空" },
 ];
+
+export const DIFFICULTY_LIST = [
+  { code: "难", name: "难" },
+  { code: "中", name: "中" },
+  { code: "易", name: "易" },
+];
+
+/**
+ * sections 必须有,这样结构容易验证和访问
+ * sections 的内容可以为 [] ,表示一个完全的空富文本
+ * 空行用 {type: "text", value: "", param: null} 来表示
+ */
+export const EMPTY_RICH_TEXT = { sections: [] };

+ 92 - 0
src/modules/question/components/QuestionEditDialog.vue

@@ -0,0 +1,92 @@
+<template>
+  <el-dialog
+    custom-class="side-dialog"
+    :visible.sync="modalIsShow"
+    :title="title"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    fullscreen
+    @open="visibleChange"
+  >
+    <div class="part-box question-edit">
+      <div class="question-type">
+        <div class="question-type-list">
+          <span>题型</span>
+          <el-button
+            v-for="item in QUESTION_TYPES"
+            :key="item.code"
+            :type="curQuestionType === item.code ? 'primary' : 'default'"
+            @click="switchType(item)"
+            >{{ item.name }}</el-button
+          >
+        </div>
+        <p class="tips-info">
+          说明:如果是综合类试题(套题)可以选择题型为阅卷理解进行录入操作。
+        </p>
+      </div>
+      <!-- question-body -->
+      <component
+        :is="structTypeComp"
+        ref="QuestionEditDetail"
+        :question="question"
+      ></component>
+    </div>
+    <div slot="footer">
+      <el-button type="primary" @click="confirm">确定</el-button>
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { QUESTION_TYPES } from "@/constants/constants";
+
+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",
+  data() {
+    return {
+      modalIsShow: false,
+      QUESTION_TYPES,
+      curQuestionType: "SINGLE_ANSWER_QUESTION",
+    };
+  },
+  computed: {
+    title() {
+      return "11";
+    },
+    structTypeComp() {
+      return structTypeCompDict[this.curQuestionType];
+    },
+  },
+  methods: {
+    visibleChange() {},
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    switchType(item) {
+      // to warning
+      this.curQuestionType = item.code;
+    },
+    confirm() {
+      this.cancel();
+    },
+  },
+};
+</script>

+ 1 - 0
src/modules/question/components/QuestionFolderDialog.vue

@@ -4,6 +4,7 @@
     :visible.sync="modalIsShow"
     :title="title"
     width="600px"
+    :modal="false"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     append-to-body

+ 1 - 0
src/modules/question/components/QuestionImportDialog.vue

@@ -4,6 +4,7 @@
     :visible.sync="modalIsShow"
     title="试题导入"
     width="700px"
+    :modal="false"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     append-to-body

+ 147 - 0
src/modules/question/components/QuestionInfoEdit.vue

@@ -0,0 +1,147 @@
+<template>
+  <div class="question-info-edit">
+    <el-form label-width="100px">
+      <el-form-item label="难度">
+        <el-select v-model="quesModel.difficulty" placeholder="请输入难度">
+          <el-option
+            v-for="item in difficultyList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="属性名">
+        <div class="box-flex">
+          <property-select
+            v-model="properties.coursePropertyId"
+            :course-id="modelForm.courseId"
+            @change="coursePropertyChange"
+          ></property-select>
+          <span>一级</span>
+          <property-sub-select
+            v-model="properties.firstPropertyId"
+            :parent-id="properties.coursePropertyId"
+            data-type="first"
+            @change="firstPropertyChange"
+          ></property-sub-select>
+          <span>二级</span>
+          <property-sub-select
+            v-model="properties.secondPropertyId"
+            :parent-id="properties.firstPropertyId"
+            data-type="second"
+            @change="secondPropertyChange"
+          ></property-sub-select>
+          <el-button
+            type="primary"
+            icon="icon icon-plus-white"
+            :disabled="!propSelected"
+            @click="addProperty"
+            >新增属性</el-button
+          >
+        </div>
+      </el-form-item>
+      <el-form-item label="属性列表">
+        <el-tag
+          v-for="item in modelForm.quesProperties"
+          :key="item.id"
+          style="margin-right: 5px"
+          closable
+          effect="dark"
+          type="primary"
+          @close="removeProperty(item)"
+        >
+          {{ item.name }}
+        </el-tag>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import { DIFFICULTY_LIST } from "@/constants/constants";
+
+export default {
+  name: "QuestionInfoEdit",
+  props: {
+    data: {
+      type: Object,
+      default() {
+        return {
+          courseId: "",
+          difficulty: "易",
+          difficultyDegree: null,
+          quesProperties: [],
+        };
+      },
+    },
+  },
+  data() {
+    return {
+      DIFFICULTY_LIST,
+      modelForm: {
+        difficulty: "易",
+        quesProperties: [],
+      },
+      properties: {
+        coursePropertyId: "",
+        firstPropertyId: "",
+        secondPropertyId: "",
+      },
+      selection: {
+        courseProperty: {},
+        firstProperty: {},
+        secondProperty: {},
+      },
+    };
+  },
+  computed: {
+    propSelected() {
+      return (
+        this.properties.coursePropertyId &&
+        this.properties.firstPropertyId &&
+        this.properties.secondPropertyId
+      );
+    },
+  },
+  methods: {
+    coursePropertyChange(val) {
+      this.selection.courseProperty = val || {};
+    },
+    firstPropertyChange(val) {
+      this.selection.firstProperty = val || {};
+    },
+    secondPropertyChange(val) {
+      this.selection.secondProperty = val || {};
+    },
+    removeProperty(property) {
+      this.modelForm.quesProperties = this.modelForm.quesProperties.filter(
+        (item) => property.id !== item.id
+      );
+      this.emitChange();
+    },
+    addProperty() {
+      if (!this.propSelected) return;
+      const newProperty = {
+        id: `${this.properties.coursePropertyId}_${this.properties.firstPropertyId}_${this.properties.secondPropertyId}`,
+        name: `${this.selection.courseProperty.name},${this.selection.firstProperty.name},${this.selection.secondProperty.name}`,
+        ...this.properties,
+      };
+      const propertyExist = this.modelForm.quesProperties.find(
+        (item) => item.id === newProperty.id
+      );
+      if (propertyExist) {
+        this.$message.error("属性已存在!");
+        return;
+      }
+      this.modelForm.quesProperties.push(newProperty);
+      this.emitChange();
+    },
+    emitChange() {
+      this.$emit("change", this.modelForm);
+    },
+  },
+};
+</script>

+ 1 - 0
src/modules/question/components/QuestionSafetySetDialog.vue

@@ -4,6 +4,7 @@
     :visible.sync="modalIsShow"
     title="安全设置"
     width="500px"
+    :modal="false"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     append-to-body

+ 1 - 0
src/modules/question/components/QuestionStatisticsDialog.vue

@@ -3,6 +3,7 @@
     class="question-statistics-dialog"
     :visible.sync="modalIsShow"
     title="试题统计"
+    :modal="false"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     append-to-body

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

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

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

@@ -0,0 +1,21 @@
+<template>
+  <div class="fill-blank-question">FillBlankQuestion-question</div>
+</template>
+
+<script>
+export default {
+  name: "FillBlankQuestion",
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {};
+  },
+  methods: {},
+};
+</script>

+ 186 - 0
src/modules/question/components/edit/SelectQuestion.vue

@@ -0,0 +1,186 @@
+<template>
+  <div class="select-question">
+    <el-form
+      ref="modalFormComp"
+      :model="modelForm"
+      :rules="rules"
+      label-width="100px"
+    >
+      <el-form-item prop="quesBody" label="题干:">
+        <v-editor v-model="modalForm.quesBody"></v-editor>
+      </el-form-item>
+      <el-form-item
+        v-for="(option, oindex) in modalForm.quesOptions"
+        :key="option.nmuber"
+        :prop="`quesOptions.${oindex}.body`"
+        :rules="optionRule"
+      >
+        <div class="question-edit-option">
+          <div class="option-check">
+            {{ (option.nmuber - 1) | optionOrderWordFilter }}
+          </div>
+          <div class="option-body">
+            <v-editor v-model="option.body"></v-editor>
+          </div>
+          <div class="option-delete">
+            <el-button
+              size="mini"
+              circle
+              type="primary"
+              icon="el-icon-add"
+              title="新增"
+              @click.prevent="addQuesOption(index)"
+            ></el-button>
+            <el-button
+              size="mini"
+              circle
+              type="danger"
+              icon="el-icon-delete"
+              title="删除"
+              @click.prevent="removeQuesOption(index)"
+            ></el-button>
+          </div>
+        </div>
+      </el-form-item>
+      <el-form-item prop="quesAnswer" label="答案">
+        <el-radio-group
+          v-if="IS_SIMPLE"
+          v-model="quesAnswer"
+          @change="answerChange"
+        >
+          <el-radio
+            v-for="option in modalForm.quesOptions"
+            :key="option.number"
+            :label="option.number"
+          >
+            {{ (option.number - 1) | optionOrderWordFilter }}
+          </el-radio>
+        </el-radio-group>
+        <el-checkbox-group
+          v-if="IS_SIMPLE"
+          v-model="quesAnswer"
+          @change="answerChange"
+        >
+          <el-checkbox
+            v-for="option in modalForm.quesOptions"
+            :key="option.number"
+            :label="option.number"
+          >
+            {{ (option.number - 1) | optionOrderWordFilter }}
+          </el-checkbox>
+        </el-checkbox-group>
+      </el-form-item>
+      <el-form-item label="答案解析">
+        <v-editor v-model="modalForm.comment"></v-editor>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import { isAnEmptyRichText } from "@/utils/utils";
+
+export default {
+  name: "SelectQuestion",
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalForm: {
+        questionType: "SINGLE_ANSWER_QUESTION",
+        quesBody: null,
+        quesOptions: [],
+        quesAnswer: [],
+        comment: null,
+      },
+      quesAnswer: null,
+      rules: {
+        quesBody: [
+          {
+            validator: (rule, value, callback) => {
+              if (!value || isAnEmptyRichText(value)) {
+                return callback(new Error(`请输入题干`));
+              }
+              callback();
+            },
+            trigger: "change",
+          },
+        ],
+        quesAnswer: [
+          {
+            validator: (rule, value, callback) => {
+              if (!value || !value.length) {
+                return callback(new Error(`请设置答案`));
+              }
+              callback();
+            },
+            trigger: "change",
+          },
+        ],
+      },
+      optionRule: {
+        validator: (rule, value, callback) => {
+          if (!value || !value.length) {
+            return callback(new Error(`请输入选项内容`));
+          }
+          callback();
+        },
+        trigger: "change",
+      },
+    };
+  },
+  computed: {
+    IS_SIMPLE() {
+      return this.question.questionType === "SINGLE_ANSWER_QUESTION";
+    },
+    answer() {
+      return "w";
+    },
+  },
+  methods: {
+    addQuesOption(index) {
+      if (this.modalForm.quesOptions.length >= 20) {
+        this.$message.error("选项最多20个");
+        return;
+      }
+      this.modalForm.quesOptions.splice(index + 1, 0, {
+        number: 0,
+        body: { sections: [] },
+      });
+      this.resetNumberAndSaveOptions();
+    },
+    removeQuesOption(index) {
+      this.modalForm.quesOptions.splice(index, 1);
+      this.resetNumberAndSaveOptions();
+    },
+    resetNumberAndSaveOptions() {
+      this.modalForm.quesOptions = this.modalForm.quesOptions.forEach(
+        (option, index) => {
+          option.number = index + 1;
+        }
+      );
+      const optionCount = this.modalForm.quesOptions.length;
+      if (this.IS_SIMPLE) {
+        if (this.quesAnswer > optionCount) this.quesAnswer = null;
+      } else {
+        this.quesAnswer = this.quesAnswer.filter((item) => item <= optionCount);
+      }
+      this.answerChange();
+    },
+    answerChange() {
+      this.modalForm.quesAnswer = this.IS_SIMPLE
+        ? [this.quesAnswer]
+        : this.quesAnswer;
+    },
+    validate() {
+      return this.$refs.modalFormComp.validate();
+    },
+  },
+};
+</script>

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

@@ -0,0 +1,13 @@
+<template>
+  <div class="text-answer-auestion">TextAnswerQuestion</div>
+</template>
+
+<script>
+export default {
+  name: "TextAnswerQuestion",
+  data() {
+    return {};
+  },
+  methods: {},
+};
+</script>

+ 118 - 0
src/modules/question/components/model/questionModel.js

@@ -0,0 +1,118 @@
+import { deepCopy } from "@/plugins/utils";
+
+export const select_question = {
+  questionType: 1, // 2多选
+  quesBody: {
+    sections: [],
+  },
+  quesOptions: [
+    {
+      number: 1,
+      body: {
+        sections: [{ blocks: [{ type: "text", value: "黑格尔" }] }],
+      },
+    },
+    {
+      number: 2,
+      body: {
+        sections: [{ blocks: [{ type: "text", value: "苏格拉底" }] }],
+      },
+    },
+  ],
+  quesAnswer: [4],
+  comment: { sections: [] },
+};
+
+export const boolean_question = {
+  questionType: 3,
+  quesBody: {
+    sections: [],
+  },
+  quesAnswer: false,
+  comment: { sections: [] },
+};
+
+export const fill_blank_question = {
+  questionType: 4,
+  quesBody: {
+    sections: [],
+  },
+  quesAnswer: [
+    {
+      index: 1,
+      sections: [{ blocks: [{ type: "text", value: "电纺丝技术" }] }],
+    },
+    {
+      index: 2,
+      sections: [{ blocks: [{ type: "text", value: "电纺丝技术" }] }],
+    },
+  ],
+  comment: { sections: [] },
+};
+
+export const text_answer_question = {
+  questionType: 5,
+  quesBody: {
+    sections: [],
+  },
+  // only one
+  quesAnswer: [
+    {
+      index: 1,
+      sections: [{ blocks: [{ type: "text", value: "电纺丝技术" }] }],
+    },
+  ],
+  comment: { sections: [] },
+};
+// 完型填空/听力/阅读理解
+export const reading_comprehension_question = {
+  questionType: 6,
+  quesBody: {
+    sections: [],
+  },
+  comment: { sections: [] },
+  subQuestions: [],
+  // answer: [{ number: 1, answer: [8] }],
+};
+
+// 段落匹配/选词填空
+export const banked_cloze_question = {
+  questionType: 8,
+  quesBody: {
+    sections: [],
+  },
+  comment: { sections: [] },
+  subQuestions: [
+    // select_question
+  ],
+  quesOptions: [
+    {
+      number: 1,
+      body: {
+        sections: [{ blocks: [{ type: "text", value: "黑格尔" }] }],
+      },
+    },
+    {
+      number: 2,
+      body: {
+        sections: [{ blocks: [{ type: "text", value: "苏格拉底" }] }],
+      },
+    },
+  ],
+  param: { matchingMode: 1, matchingType: 1 },
+  // 段落匹配: "param": { "matchingMode": 2, "matchingType": 2 },
+  quesAnswer: [{ number: 1, answer: [8] }],
+};
+
+const models = {
+  select_question,
+  boolean_question,
+  fill_blank_question,
+  text_answer_question,
+  reading_comprehension_question,
+  banked_cloze_question,
+};
+
+export const getInitQuestionModel = (qtype) => {
+  return deepCopy(models[qtype]);
+};

+ 2 - 2
src/modules/questions/routes/routes.js

@@ -7,8 +7,8 @@ import InsertBluePaperStructure from "../views/InsertBluePaperStructure.vue";
 import InsertBluePaperStructureInfo from "../views/InsertBluePaperStructureInfo.vue";
 import CourseProperty from "../views/CourseProperty.vue";
 import PropertyInfo from "../views/PropertyInfo.vue";
-// import ImportPaper from "../views/ImportPaper.vue";
-import ImportPaper from "../../question/views/QuestionManage";
+import ImportPaper from "../views/ImportPaper.vue";
+// import ImportPaper from "../../question/views/QuestionManage";
 import GenPaper from "../views/GenPaper.vue";
 import ImportPaperInfo from "../views/ImportPaperInfo.vue";
 import GenPaperDetail from "../views/GenPaperDetail.vue";

+ 27 - 0
src/utils/utils.js

@@ -3,3 +3,30 @@ import queryString from "query-string";
 export function object2QueryString(obj) {
   return queryString.stringify(obj);
 }
+
+/**
+ * 判断富文本是否是一个空,用来判断富文本是否为空
+ *
+ * @param {cont} 富文本对象
+ */
+export function isAnEmptyRichText(cont) {
+  if (!cont) return true;
+
+  if (!cont.sections || !cont.sections.length) return true;
+
+  let blocks = [];
+  cont.sections.forEach((section) => {
+    blocks = [...blocks, ...section.blocks];
+  });
+
+  if (!blocks.length) return true;
+
+  if (blocks.some((item) => item.type !== "text")) {
+    return false;
+  } else {
+    return !blocks
+      .map((item) => item.value)
+      .join("")
+      .trim();
+  }
+}