Browse Source

答题判断正误

dusenyao 10 months ago
parent
commit
4e9df71bd5

+ 86 - 1
src/views/book/courseware/preview/components/fill/FillPreview.vue

@@ -13,9 +13,12 @@
               <el-input
                 :key="j"
                 v-model="li.content"
-                :class="[data.property.fill_font]"
+                :class="[data.property.fill_font, ...computedAnswerClass(li.mark)]"
                 :style="[{ width: Math.max(80, li.content.length * 21.3) + 'px' }]"
               />
+              <span v-show="computedAnswerText(li.mark).length > 0" :key="`answer-${j}`" class="right-answer">
+                {{ computedAnswerText(li.mark) }}
+              </span>
             </template>
           </template>
         </p>
@@ -54,6 +57,27 @@ export default {
       return fillFontList.find(({ value }) => this.data.property.fill_font === value).font;
     },
   },
+  watch: {
+    'data.model_essay': {
+      handler(list) {
+        this.answer.answer_list = list
+          .map((item) => {
+            return item
+              .map(({ type, content, mark }) => {
+                if (type === 'input') {
+                  return {
+                    value: content,
+                    mark,
+                  };
+                }
+              })
+              .filter((item) => item);
+          })
+          .flat();
+      },
+      deep: true,
+    },
+  },
   created() {
     this.answer.answer_list = this.data.model_essay
       .map((item) => {
@@ -93,6 +117,45 @@ export default {
       };
       return style;
     },
+    /**
+     * 计算答题对错选项字体颜色
+     * @param {string} mark 选项标识
+     */
+    computedAnswerClass(mark) {
+      if (!this.isJudgingRightWrong && !this.isShowRightAnswer) {
+        return '';
+      }
+      let selectOption = this.answer.answer_list.find((item) => item.mark === mark);
+      let answerOption = this.data.answer.answer_list.find((item) => item.mark === mark);
+      if (!selectOption) return '';
+      let selectValue = selectOption.value;
+      let answerValue = answerOption.value;
+      let classList = [];
+      let isRight = selectValue === answerValue;
+      if (this.isJudgingRightWrong) {
+        isRight ? classList.push('right') : classList.push('wrong');
+      }
+
+      if (this.isShowRightAnswer && !isRight) {
+        classList.push('show-right-answer');
+      }
+      return classList;
+    },
+    /**
+     * 计算正确答案文本
+     * @param {string} mark 选项标识
+     */
+    computedAnswerText(mark) {
+      if (!this.isShowRightAnswer) return '';
+      let selectOption = this.answer.answer_list.find((item) => item.mark === mark);
+      let answerOption = this.data.answer.answer_list.find((item) => item.mark === mark);
+      if (!selectOption) return '';
+      let selectValue = selectOption.value;
+      let answerValue = answerOption.value;
+      let isRight = selectValue === answerValue;
+      if (isRight) return '';
+      return `(${answerValue})`;
+    },
   },
 };
 </script>
@@ -134,6 +197,28 @@ export default {
         font-family: 'arial', sans-serif;
       }
 
+      &.right {
+        :deep input.el-input__inner {
+          color: $right-color;
+        }
+      }
+
+      &.wrong {
+        :deep input.el-input__inner {
+          color: $error-color;
+        }
+      }
+
+      & + .right-answer {
+        position: relative;
+        left: -4px;
+        display: inline-block;
+        height: 32px;
+        line-height: 28px;
+        vertical-align: bottom;
+        border-bottom: 1px solid $font-color;
+      }
+
       :deep input.el-input__inner {
         padding: 0;
         font-size: 16pt;

+ 115 - 4
src/views/book/courseware/preview/components/matching/MatchingPreview.vue

@@ -9,7 +9,7 @@
           <div
             v-for="({ content, mark }, j) in item"
             :key="mark"
-            :class="['item-wrapper', `item-${mark}`]"
+            :class="['item-wrapper', `item-${mark}`, computedAnswerClass(mark)]"
             :style="{ cursor: disabled ? 'default' : 'pointer' }"
             @mousedown="mousedown($event, i, j, mark)"
             @mouseup="mouseup($event, i, j, mark)"
@@ -69,6 +69,63 @@ export default {
       },
       immediate: true,
     },
+    answerList: {
+      handler(list) {
+        let arr = [];
+        let column_number = this.data.property.column_num;
+        // 从前往后遍历,如果有 nextMark,就往后找,直到没有 nextMark
+        // 如果第一个选项的 nextMark 为空,则整个行都为空,等待后面的 preArr 填充
+        list.forEach((item, i) => {
+          let { mark, nextMark } = item[0];
+          arr[i] = nextMark ? [mark, nextMark] : new Array(column_number).fill('');
+          let _nextMark = nextMark;
+          while (_nextMark) {
+            let fMark = this.findMark(list, _nextMark, 'next');
+            if (column_number > arr[i].length) {
+              arr[i].push(fMark);
+            }
+            _nextMark = fMark;
+          }
+        });
+        let emptyStringArray = []; // 无数据的列的下标数组
+        arr.forEach((item, i) => {
+          item.every((li) => li.length <= 0) ? emptyStringArray.push(i) : '';
+        });
+        if (column_number === 2) {
+          this.answer.answer_list = arr;
+          return;
+        }
+        // 从后向前遍历,如果有 preMark,就往前找,直到没有 preMark,并过滤掉无数据的列
+        let preArr = [];
+        list.forEach((item) => {
+          let { mark, preMark } = item[column_number - 1];
+          let _arr = new Array(column_number).fill('');
+          let back = column_number - 1;
+          let _preMark = preMark;
+          while (_preMark) {
+            _arr[back] = mark;
+            back -= 1;
+            _arr[back] = _preMark;
+            back -= 1;
+            let fMark = this.findMark(list, _preMark, 'pre');
+            if (back >= 0 && fMark) {
+              _arr[back] = fMark;
+            }
+            _preMark = fMark;
+          }
+          if (_arr.every((li) => li.length <= 0) || _arr.every((li) => li.length > 0)) return;
+          preArr.push(_arr);
+        });
+        // 将 preArr 中的数据填充到 arr 无数据的位置中
+        if (preArr.length > 0 && emptyStringArray.length > 0) {
+          preArr.forEach((item, i) => {
+            arr[emptyStringArray[i]] = item;
+          });
+        }
+        this.answer.answer_list = arr;
+      },
+      deep: true,
+    },
     isJudgingRightWrong(cur) {
       if (cur) {
         this.clearLine();
@@ -81,7 +138,6 @@ export default {
       if (cur) {
         this.$nextTick(() => {
           this.circulateAnswerList(true);
-          this.judgeRichTextIsImgAndText(true);
         });
       }
     },
@@ -232,7 +288,8 @@ export default {
     circulateAnswerList(isShowRightAnswer = false) {
       let answer_list = isShowRightAnswer ? this.data.answer.answer_list : this.answer.answer_list;
       answer_list.forEach((item) => {
-        item.forEach((mark, j) => {
+        item.forEach((_m, j) => {
+          let mark = isShowRightAnswer ? _m.mark : _m;
           if (mark.length <= 0 || j >= item.length - 1) return;
 
           let cur = { i: -1, j: -1 }; // 当前连线点
@@ -245,7 +302,7 @@ export default {
             });
           });
           this.curConnectionPoint = { i: cur.i, j: cur.j, mark };
-          this.createLine(item[j + 1], false, isShowRightAnswer);
+          this.createLine(item[j + 1].mark, false, isShowRightAnswer);
         });
       });
     },
@@ -385,6 +442,60 @@ export default {
         }),
       );
     },
+    /**
+     * 根据 mark 查找 nextMark 或 preMark
+     * @param {Array} list 答案列表
+     * @param {String} mark 标记
+     * @param {'pre'|'next'} type 类型
+     * @returns {String} 返回 nextMark 或 preMark
+     */
+    findMark(list, mark, type) {
+      let fMark = '';
+      list.find((item) => {
+        return item.find((li) => {
+          if (mark === li.mark) {
+            fMark = type === 'pre' ? li.preMark : li.nextMark;
+            return true;
+          }
+        });
+      });
+      return fMark;
+    },
+    /**
+     * 计算答题对错选项class
+     * @param {string} mark 选项标识
+     */
+    computedAnswerClass(mark) {
+      if (!this.isJudgingRightWrong) return '';
+      let answer = this.data.answer.answer_list.find((item) => {
+        return item.some((li) => li.mark === mark);
+      });
+      return this.is2DArrayContains1DArray(this.answer.answer_list, answer) ? 'right' : 'wrong';
+    },
+    /**
+     * 判断二维数组是否包含一维数组
+     * @param {array} arr2D 二维数组
+     * @param {array} arr1D 一维数组
+     */
+    is2DArrayContains1DArray(arr2D, arr1D) {
+      for (let i = 0; i < arr2D.length; i++) {
+        const currentArray = arr2D[i];
+        if (currentArray.length !== arr1D.length) {
+          continue;
+        }
+        let found = true;
+        for (let j = 0; j < currentArray.length; j++) {
+          if (currentArray[j] !== arr1D[j]) {
+            found = false;
+            break;
+          }
+        }
+        if (found) {
+          return true;
+        }
+      }
+      return false;
+    },
   },
 };
 </script>

+ 131 - 4
src/views/book/courseware/preview/components/sort/SortPreview.vue

@@ -5,19 +5,42 @@
 
     <div class="main">
       <ul class="option-list">
-        <draggable v-model="data.option_list" animation="300">
+        <draggable v-model="move_list" animation="300" @start="onStart($event)" @end="onEnd($event)">
           <transition-group
             class="group"
             :style="{
               flexDirection: data.property.arrange_direction === arrangeTypeList[0].value ? 'row' : 'column',
             }"
           >
-            <li v-for="(item, i) in data.option_list" :key="i">
+            <li
+              v-for="(item, i) in move_list"
+              :key="i"
+              :class="['drag-item', ...computedDragItemClass(i)]"
+              @click="handleClickItem(i)"
+            >
               <span class="rich-text" v-html="sanitizeHTML(item.content)"></span>
             </li>
           </transition-group>
         </draggable>
       </ul>
+
+      <template v-if="isShowRightAnswer && !is_all_right">
+        <div class="right-title">正确答案:</div>
+        <ul class="option-list">
+          <draggable v-model="move_list" animation="300" :disabled="true">
+            <transition-group
+              class="group"
+              :style="{
+                flexDirection: data.property.arrange_direction === arrangeTypeList[0].value ? 'row' : 'column',
+              }"
+            >
+              <li v-for="(item, i) in move_list" :key="i" :class="['drag-item', 'right']">
+                <span class="rich-text" v-html="sanitizeHTML(item.content)"></span>
+              </li>
+            </transition-group>
+          </draggable>
+        </ul>
+      </template>
     </div>
   </div>
 </template>
@@ -37,9 +60,92 @@ export default {
     return {
       data: getSortData(),
       arrangeTypeList,
+      move_list: [], // 移动后的数组
+      drag: false,
+      clickIndexList: [], // 点击选中的索引
+      is_all_right: false, // 是否全对
     };
   },
-  methods: {},
+  watch: {
+    move_list: {
+      handler(val) {
+        if (!val || this.isJudgingRightWrong) return;
+        this.answer.answer_list = val.map(({ mark }) => mark);
+      },
+      deep: true,
+    },
+    isJudgingRightWrong: {
+      handler(val) {
+        if (!val) return;
+        this.move_list = this.answer.answer_list.map((mark) =>
+          this.data.option_list.find((item) => item.mark === mark),
+        );
+        this.is_all_right = this.move_list.every(
+          ({ custom_serial_number }, index) => index + 1 === Number(custom_serial_number),
+        );
+      },
+      immediate: true,
+    },
+    'data.option_list': {
+      handler(val) {
+        if (!val) return;
+        this.move_list = JSON.parse(JSON.stringify(val));
+      },
+      deep: true,
+      immediate: true,
+    },
+  },
+  methods: {
+    onStart() {
+      this.drag = true;
+    },
+    // 拖拽结束事件
+    onEnd() {
+      this.drag = false;
+      this.clickIndexList = [];
+    },
+    /**
+     * 计算拖拽项目样式
+     * @param {number} index 索引
+     */
+    computedDragItemClass(index) {
+      return [
+        {
+          selected: this.clickIndexList.includes(index),
+        },
+        this.isJudgingRightWrong
+          ? Number(this.move_list[index].custom_serial_number) === index + 1
+            ? 'right'
+            : 'error'
+          : '',
+      ];
+    },
+    /**
+     * 处理点击项目事件
+     * @param {number} index 索引
+     */
+    handleClickItem(index) {
+      if (this.disabled) return;
+      if (this.clickIndexList.length === 1) {
+        if (this.clickIndexList[0] === index) {
+          this.clickIndexList = [];
+          return;
+        }
+      }
+      this.clickIndexList.push(index);
+      if (this.clickIndexList.length === 2) {
+        this.changeSort();
+      }
+    },
+    // 点击交换
+    changeSort() {
+      const index1 = this.clickIndexList[0];
+      const index2 = this.clickIndexList[1];
+      [this.move_list[index1], this.move_list[index2]] = [this.move_list[index2], this.move_list[index1]];
+      this.clickIndexList = [];
+      this.answer.answer_list = this.move_list.map(({ mark }) => mark);
+    },
+  },
 };
 </script>
 
@@ -55,15 +161,36 @@ export default {
       flex-wrap: wrap;
       gap: 8px;
 
-      li {
+      .drag-item {
         min-height: 40px;
         padding: 8px 16px;
         cursor: move;
         background: $content-color;
         border: 1px solid $content-color;
         border-radius: 4px;
+
+        &.selected {
+          border-color: $light-main-color;
+        }
+
+        &.error {
+          border-color: $error-color;
+        }
+
+        &.right {
+          background-color: $right-bc-color;
+          border-color: $right-color;
+        }
       }
     }
   }
+
+  .right-title {
+    margin: 24px 0;
+    font-size: 16px;
+    font-weight: 500;
+    line-height: 24px;
+    color: #000;
+  }
 }
 </style>