瀏覽代碼

手动组卷

zhangjie 2 年之前
父節點
當前提交
b6e2ba96d9

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

@@ -356,3 +356,14 @@ body {
 .margin_left_10 {
   margin-left: 10px;
 }
+.inline-middle {
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.color-primary {
+  color: $--color-primary;
+}
+.color-danger {
+  color: $--color-danger;
+}

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

@@ -971,3 +971,27 @@
     width: 180px;
   }
 }
+// build-paper-manual
+.build-paper-manual {
+  .detail-header {
+    flex-grow: 2;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding-right: 10px;
+    h3 > span {
+      margin: 0 3px;
+    }
+
+    .el-button {
+      font-size: 16px;
+    }
+  }
+  .detail-desc {
+    margin-bottom: 10px;
+  }
+  .el-collapse-item__content {
+    padding-left: 10px;
+    padding-right: 10px;
+  }
+}

+ 8 - 3
src/modules/paper/components/BuildPaperAuto.vue

@@ -12,7 +12,7 @@
         :name="index"
       >
         <div slot="title" class="subq-header">
-          <h3>{{ index + 1 }}、{{ subq.name }}</h3>
+          <h3>{{ index + 1 }}、{{ detail.name }}</h3>
           <div>
             <el-button
               size="mini"
@@ -65,8 +65,13 @@ export default {
     editDetail(curDetail) {
       this.curDetail = curDetail;
     },
-    removeDetail(index) {
-      console.log(index);
+    async removeDetail(index) {
+      const confirm = await this.$confirm(`确定要删除当前大题吗?`, "提示", {
+        type: "warning",
+      }).catch(() => {});
+      if (confirm !== "confirm") return;
+
+      this.details.splice(index, 1);
     },
     detailModified(data) {
       const index = this.details.findIndex((item) => item.id === data.id);

+ 150 - 3
src/modules/paper/components/BuildPaperManual.vue

@@ -1,13 +1,160 @@
 <template>
-  <div class="build-paper-manual">BuildPaperManual</div>
+  <div class="build-paper-manual">
+    <div class="build-step-title">
+      <h3><span>大题设置</span></h3>
+      <el-button
+        type="primary"
+        size="small"
+        icon="el-icon-circle-plus-outline"
+        @click="addDetail"
+        >新增大题</el-button
+      >
+    </div>
+
+    <el-collapse v-model="activeNames">
+      <el-collapse-item
+        v-for="(detail, dindex) in details"
+        :key="detail.id"
+        :name="detail.id"
+      >
+        <div slot="title" class="detail-header">
+          <h3>
+            {{ dindex + 1 }}、{{ detail.name }}(题目数量
+            <span class="color-primary">{{ detail.questions.length }}</span
+            >,每题分数<span class="color-primary">{{
+              detail.scorePerQuestion
+            }}</span
+            >)
+          </h3>
+          <div>
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-edit"
+              title="编辑"
+              @click.stop="editDetail(detail)"
+            ></el-button>
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-delete"
+              title="删除"
+              @click.stop="removeDetail(dindex)"
+            ></el-button>
+          </div>
+        </div>
+        <div class="detail-desc">
+          <rich-text :text-json="detail.remark"></rich-text>
+        </div>
+        <div class="detail-questions">
+          <template v-for="(question, qindex) in detail.questions">
+            <build-paper-question-base
+              v-if="checkIsBaseQuestion(question.questionType)"
+              :key="question.id"
+              v-model="question.score"
+              :question="question"
+              :question-serialno="qindex"
+              @on-edit="toEditQuestion"
+              @on-remove="toRemoveQuestion"
+            >
+            </build-paper-question-base>
+            <build-paper-question-nested
+              v-else
+              :key="question.id"
+              :question="question"
+              :question-serialno="qindex"
+              @on-edit="toEditQuestion"
+              @on-remove="toRemoveQuestion"
+            ></build-paper-question-nested>
+          </template>
+        </div>
+        <el-button type="primary" @click="toAddQuestion">新增小题</el-button>
+      </el-collapse-item>
+    </el-collapse>
+
+    <!-- ModifyDetailStruct -->
+    <modify-detail-struct
+      ref="ModifyDetailStruct"
+      :detail="curDetail"
+      @modified="detailModified"
+    ></modify-detail-struct>
+  </div>
 </template>
 
 <script>
+import ModifyDetailStruct from "../components/ModifyDetailStruct.vue";
+import BuildPaperQuestionBase from "./BuildPaperQuestionBase.vue";
+import BuildPaperQuestionNested from "./BuildPaperQuestionNested.vue";
+import { BASE_QUESTION_TYPES } from "@/constants/constants";
+
 export default {
   name: "BuildPaperManual",
+  components: {
+    ModifyDetailStruct,
+    BuildPaperQuestionBase,
+    BuildPaperQuestionNested,
+  },
   data() {
-    return {};
+    return {
+      details: [],
+      curDetail: {},
+      activeNames: [],
+    };
+  },
+  methods: {
+    checkIsBaseQuestion(questionType) {
+      const qtype = BASE_QUESTION_TYPES.find(
+        (item) => item.code === questionType
+      );
+      return !!qtype;
+    },
+    addDetail() {
+      this.curDetail = {};
+      this.$refs.ModifyDetailStruct.open();
+    },
+    editDetail(curDetail) {
+      this.curDetail = curDetail;
+      this.$refs.ModifyDetailStruct.open();
+    },
+    async removeDetail(index) {
+      const confirm = await this.$confirm(`确定要删除当前大题吗?`, "提示", {
+        type: "warning",
+      }).catch(() => {});
+      if (confirm !== "confirm") return;
+
+      const delDetailId = this.details[index].id;
+      this.activeNames = this.activeNames.filter(
+        (item) => item !== delDetailId
+      );
+      this.details.splice(index, 1);
+    },
+    detailModified(data) {
+      const index = this.details.findIndex((item) => item.id === data.id);
+      if (index === -1) {
+        this.details.push({ ...data, questions: [] });
+        if (!this.activeNames.includes(data.id)) this.activeNames.push(data.id);
+      } else {
+        this.$set(
+          this.details,
+          index,
+          Object.assign({}, this.details[index], data)
+        );
+      }
+      console.log(this.details);
+    },
+    toAddQuestion() {
+      console.log("add");
+    },
+    toEditQuestion(question) {
+      this.curQuestion = question;
+      // todo edit question
+    },
+    toRemoveQuestion(question) {
+      this.details.forEach((detail) => {
+        const qindex = detail.questions.findIndex((q) => q.id === question.id);
+        if (qindex !== -1) detail.splice(qindex, 1);
+      });
+    },
   },
-  methods: {},
 };
 </script>

+ 99 - 0
src/modules/paper/components/BuildPaperQuestionBase.vue

@@ -0,0 +1,99 @@
+<template>
+  <div class="detail-question-part">
+    <div class="detail-question-header">
+      <div>
+        <el-tag effect="dark">{{
+          question.questionType | questionType
+        }}</el-tag>
+        <span class="inline-middle margin-left-10">小题分数:</span>
+        <el-input-number
+          v-model="score"
+          placeholder="请输入小题分数"
+          :step="0.5"
+          :min="0.5"
+          :max="999"
+          :controls="false"
+          :precision="0.1"
+          step-strictly
+          @change="scoreChange"
+        >
+        </el-input-number>
+      </div>
+      <div v-if="showAction">
+        <el-button
+          size="mini"
+          type="text"
+          icon="el-icon-delete"
+          title="编辑"
+          @click.stop="editQuestion"
+        ></el-button>
+        <el-button
+          size="mini"
+          type="text"
+          icon="el-icon-delete"
+          title="删除"
+          @click.stop="removeQuestion"
+        ></el-button>
+      </div>
+    </div>
+    <div class="detail-quesion-body">
+      <question-base-preview
+        :question="question"
+        :question-serialno="questionSerialno"
+      ></question-base-preview>
+    </div>
+  </div>
+</template>
+
+<script>
+import QuestionBasePreview from "../../question/components/QuestionBasePreview.vue";
+
+export default {
+  name: "BuildPaperQuestionBase",
+  components: { QuestionBasePreview },
+  props: {
+    value: {
+      type: Number,
+      default: undefined,
+    },
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    questionSerialno: {
+      type: [String, Number],
+      default: null,
+    },
+    showAction: {
+      type: Boolean,
+      default: true,
+    },
+  },
+  data() {
+    return {
+      score: "",
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.score = val;
+      },
+    },
+  },
+  methods: {
+    scoreChange() {
+      this.$emit("input", this.score);
+    },
+    editQuestion() {
+      this.$emit("on-edit", this.question);
+    },
+    removeQuestion() {
+      this.$emit("on-remove", this.question);
+    },
+  },
+};
+</script>

+ 120 - 0
src/modules/paper/components/BuildPaperQuestionNested.vue

@@ -0,0 +1,120 @@
+<template>
+  <div class="detail-question-part">
+    <div class="detail-question-header">
+      <div>
+        <el-tag effect="dark">{{
+          question.questionType | questionType
+        }}</el-tag>
+        <span class="inline-middle margin-left-10">小题分数:</span>
+        <el-input-number
+          v-model="score"
+          placeholder="请输入小题分数"
+          :step="0.5"
+          :min="0.5"
+          :max="999"
+          :controls="false"
+          :precision="0.1"
+          step-strictly
+          @change="scoreChange"
+        >
+        </el-input-number>
+      </div>
+      <div>
+        <el-button
+          size="mini"
+          type="text"
+          icon="el-icon-delete"
+          title="编辑"
+          @click.stop="editQuestion"
+        ></el-button>
+        <el-button
+          size="mini"
+          type="text"
+          icon="el-icon-delete"
+          title="删除"
+          @click.stop="removeQuestion"
+        ></el-button>
+      </div>
+    </div>
+    <div v-if="quesBody" class="detail-question-qbody">
+      <rich-text :text-json="quesBody"></rich-text>
+    </div>
+    <div class="detail-quesion-subqs">
+      <template v-for="(subq, sindex) in question.subQuestions">
+        <build-paper-question-base
+          :key="subq.id"
+          v-model="subq.score"
+          :question="subq"
+          :question-serialno="sindex"
+          :show-action="false"
+        ></build-paper-question-base>
+      </template>
+    </div>
+  </div>
+</template>
+
+<script>
+import BuildPaperQuestionBase from "./BuildPaperQuestionBase.vue";
+import { deepCopy } from "@/plugins/utils";
+
+export default {
+  name: "BuildPaperQuestionNested",
+  components: { BuildPaperQuestionBase },
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    questionSerialno: {
+      type: [String, Number],
+      default: "",
+    },
+  },
+  data() {
+    return {
+      score: "",
+      quesBody: null,
+    };
+  },
+  created() {
+    if (this.questionSerialno) {
+      if (this.question.quesBody) {
+        this.quesBody = deepCopy(this.question.quesBody);
+        this.quesBody.sections[0].blocks.unshift({
+          type: "text",
+          value: this.questionSerialno,
+        });
+      } else {
+        this.quesBody = {
+          sections: [
+            {
+              blocks: [
+                {
+                  type: "text",
+                  value: this.questionSerialno,
+                },
+              ],
+            },
+          ],
+        };
+      }
+    }
+  },
+  methods: {
+    scoreChange() {
+      // 这里通过引用关系直接修改了小题分值
+      this.question.subQuestion.forEach((subq) => {
+        subq.score = this.score;
+      });
+    },
+    editQuestion() {
+      this.$emit("on-edit", this.question);
+    },
+    removeQuestion() {
+      this.$emit("on-remove", this.question);
+    },
+  },
+};
+</script>

+ 3 - 3
src/modules/paper/components/BuildPaperSimple.vue

@@ -53,11 +53,11 @@
           <el-input-number
             v-model="scope.row.questionScore"
             placeholder="请输入抽取试题分值"
-            :step="0.5"
-            :min="1"
+            :step="0.1"
+            :min="0.1"
             :max="999"
             :controls="false"
-            :precision="0.1"
+            :precision="1"
             step-strictly
           ></el-input-number>
         </template>

+ 27 - 12
src/modules/paper/components/ModifyDetailStruct.vue

@@ -6,15 +6,13 @@
     :modal="false"
     append-to-body
     custom-class="side-dialog"
+    @open="visibleChange"
   >
     <el-form
-      ref="paperDetailStructForm"
+      ref="modalFormComp"
       :model="modalForm"
       :rules="rules"
-      label-position="right"
       label-width="90px"
-      inline-message
-      class="form-tight"
     >
       <el-form-item label="大题名称" prop="name">
         <el-input
@@ -38,12 +36,12 @@
       <el-form-item label="每题分值" prop="scorePerQuestion">
         <el-input-number
           v-model="modalForm.scorePerQuestion"
-          placeholder="请输入抽取试题分值"
-          :step="0.5"
-          :min="0.5"
+          placeholder="请输入题分值"
+          :step="0.1"
+          :min="0.1"
           :max="999"
           :controls="false"
-          :precision="0.1"
+          :precision="1"
           step-strictly
         ></el-input-number>
       </el-form-item>
@@ -56,12 +54,14 @@
 </template>
 
 <script>
+import { randomCode } from "@/plugins/utils";
+
 const initModalForm = {
-  id: "",
+  id: null,
   name: "",
   remark: "",
   questionType: null,
-  scorePerQuestion: undefined,
+  scorePerQuestion: 1,
 };
 export default {
   name: "ModifyDetailStruct",
@@ -85,13 +85,26 @@ export default {
             trigger: "change",
           },
         ],
+        questionType: [
+          {
+            required: true,
+            message: "请选择题型",
+            trigger: "change",
+          },
+        ],
+        scorePerQuestion: [
+          {
+            required: true,
+            message: "请输入每题分值",
+            trigger: "change",
+          },
+        ],
       },
     };
   },
   methods: {
     visibleChange() {
       this.modalForm = this.$objAssign(initModalForm, this.detail);
-      this.fileData = {};
     },
     cancel() {
       this.modalIsShow = false;
@@ -103,7 +116,9 @@ export default {
       const valid = await this.$refs.modalFormComp.validate().catch(() => {});
       if (!valid) return;
 
-      this.$emit("modified", this.modalForm);
+      let data = { ...this.modalForm };
+      if (!data.id) data.id = randomCode();
+      this.$emit("modified", data);
       this.cancel();
     },
   },

+ 1 - 1
src/modules/paper/views/BuildPaper.vue

@@ -99,7 +99,7 @@ export default {
         paperName: "",
         genNumber: 1,
         topicRepeat: false,
-        genModelType: "simple",
+        genModelType: "manual",
       },
       rules: {
         paperName: [

+ 121 - 0
src/modules/question/components/QuestionBasePreview.vue

@@ -0,0 +1,121 @@
+<template>
+  <div class="question-preview">
+    <div v-if="quesBody" class="question-body">
+      <rich-text :text-json="quesBody"></rich-text>
+    </div>
+    <div
+      v-if="question.options && question.options.length"
+      class="question-options"
+    >
+      <rich-text
+        v-for="option in question.options"
+        :key="option.number"
+        class="question-option"
+        :text-json="option.body"
+      ></rich-text>
+    </div>
+    <div class="question-answer">
+      <h4 class="question-answer-title">答案</h4>
+      <div class="question-answer-body">
+        <div v-if="IS_SELECTION">
+          {{ question.quesAnswer | selectionAnswerFilter }}
+        </div>
+        <div v-else-if="IS_BOOL_ANSWER_QUESTION">
+          {{ question.quesAnswer === "true" ? "对" : "错" }}
+        </div>
+        <div v-else-if="IS_FILL_BLANK_QUESTION || IS_TEXT_ANSWER_QUESTION">
+          <rich-text
+            v-for="(cont, cindex) in textQuesAnswer"
+            :key="cindex"
+            :text-json="cont"
+          ></rich-text>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { deepCopy } from "@/plugins/utils";
+
+export default {
+  name: "QuestionBasePreview",
+  filters: {
+    selectionAnswerFilter(val) {
+      return val.map((value) => String.fromCharCode(64 + value)).join("");
+    },
+  },
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    questionSerialno: {
+      type: [String, Number],
+      default: null,
+    },
+  },
+  data() {
+    return {
+      quesBody: null,
+      textQuesAnswer: [],
+    };
+  },
+  computed: {
+    IS_FILL_BLANK_QUESTION() {
+      return this.question.questionType === "FILL_BLANK_QUESTION";
+    },
+    IS_SELECTION() {
+      return (
+        this.question.questionType === "SINGLE_ANSWER_QUESTION" ||
+        this.question.questionType === "MULTIPLE_ANSWER_QUESTION"
+      );
+    },
+    IS_TEXT_ANSWER_QUESTION() {
+      return this.question.questionType === "TEXT_ANSWER_QUESTION";
+    },
+    IS_BOOL_ANSWER_QUESTION() {
+      return this.question.questionType === "BOOL_ANSWER_QUESTION";
+    },
+  },
+  created() {
+    if (this.questionSerialno) {
+      if (this.question.quesBody) {
+        this.quesBody = deepCopy(this.question.quesBody);
+        this.quesBody.sections[0].blocks.unshift({
+          type: "text",
+          value: this.questionSerialno,
+        });
+      } else {
+        this.quesBody = {
+          sections: [
+            {
+              blocks: [
+                {
+                  type: "text",
+                  value: this.questionSerialno,
+                },
+              ],
+            },
+          ],
+        };
+      }
+    }
+    if (this.IS_FILL_BLANK_QUESTION) {
+      this.textQuesAnswer = this.question.quesAnswer.map((item) => {
+        let nitem = deepCopy(item);
+        nitem.sections[0].blocks.unshift({
+          type: "text",
+          value: `(${item.index})`,
+        });
+        return nitem;
+      });
+    } else if (this.IS_TEXT_ANSWER_QUESTION) {
+      this.textQuesAnswer = this.question.quesAnswer;
+    }
+  },
+  methods: {},
+};
+</script>

+ 2 - 3
src/modules/question/components/edit/BankedClozeQuestion.vue

@@ -211,10 +211,9 @@ export default {
       }).catch(() => {});
       if (res !== "confirm") return;
 
-      this.modalForm.subQuestions.splice(index, 1);
-      const delQuestionKey =
-        this.modalForm.subQuestions[index].newQuestion.questionKey;
+      const delQuestionKey = this.modalForm.subQuestions[index].questionKey;
       this.activeNames = this.activeNames.filter((k) => k !== delQuestionKey);
+      this.modalForm.subQuestions.splice(index, 1);
     },
     answerPointsChange(answerPointsChanged) {
       let subQuestions = [];

+ 2 - 3
src/modules/question/components/edit/NestedQuestion.vue

@@ -178,10 +178,9 @@ export default {
       }).catch(() => {});
       if (res !== "confirm") return;
 
-      this.modalForm.subQuestions.splice(index, 1);
-      const delQuestionKey =
-        this.modalForm.subQuestions[index].newQuestion.questionKey;
+      const delQuestionKey = this.modalForm.subQuestions[index].questionKey;
       this.activeNames = this.activeNames.filter((k) => k !== delQuestionKey);
+      this.modalForm.subQuestions.splice(index, 1);
     },
     answerPointsChange(answerPointsChanged) {
       let subQuestions = [];

+ 3 - 7
src/modules/question/components/model/questionModel.js

@@ -125,13 +125,9 @@ export const getInitQuestionModel = (qtype) => {
 };
 
 export const getMatchQuestionModel = () => {
-  return {
-    number: 1,
-    courseId: "",
-    quesAnswer: null,
-    difficulty: "易",
-    quesProperties: [],
-  };
+  let matchQuestionModel = getInitQuestionModel("SINGLE_ANSWER_QUESTION");
+  matchQuestionModel.quesOptions = null;
+  return matchQuestionModel;
 };
 
 export const STRUCT_TYPE_COMP_DICT = {