Kaynağa Gözat

答题控制、阅读题删除分值

dusenyao 1 yıl önce
ebeveyn
işleme
ddf77aac38

+ 9 - 9
package-lock.json

@@ -1840,9 +1840,9 @@
       "dev": true
     },
     "@rushstack/eslint-patch": {
-      "version": "1.6.1",
-      "resolved": "https://registry.npmmirror.com/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz",
-      "integrity": "sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==",
+      "version": "1.7.0",
+      "resolved": "https://registry.npmmirror.com/@rushstack/eslint-patch/-/eslint-patch-1.7.0.tgz",
+      "integrity": "sha512-Jh4t/593gxs0lJZ/z3NnasKlplXT2f+4y/LZYuaKZW5KAaiVFL/fThhs+17EbUd53jUVJ0QudYCBGbN/psvaqg==",
       "dev": true
     },
     "@sideway/address": {
@@ -8966,9 +8966,9 @@
       "dev": true
     },
     "prettier": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.2.2.tgz",
-      "integrity": "sha512-HTByuKZzw7utPiDO523Tt2pLtEyK7OibUD9suEJQrPUCYQqrHr74GGX6VidMrovbf/I50mPqr8j/II6oBAuc5A==",
+      "version": "3.2.4",
+      "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.2.4.tgz",
+      "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
       "dev": true
     },
     "prettier-linter-helpers": {
@@ -9447,9 +9447,9 @@
       "dev": true
     },
     "sass": {
-      "version": "1.69.7",
-      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.7.tgz",
-      "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==",
+      "version": "1.70.0",
+      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.70.0.tgz",
+      "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==",
       "dev": true,
       "requires": {
         "chokidar": ">=3.0.0 <4.0.0",

+ 3 - 3
package.json

@@ -29,7 +29,7 @@
   "devDependencies": {
     "@babel/core": "^7.23.7",
     "@babel/eslint-parser": "^7.23.3",
-    "@rushstack/eslint-patch": "^1.6.1",
+    "@rushstack/eslint-patch": "^1.7.0",
     "@types/md5": "^2.3.5",
     "@vue/cli-plugin-babel": "~5.0.8",
     "@vue/cli-plugin-eslint": "~5.0.8",
@@ -43,8 +43,8 @@
     "eslint-plugin-vue": "^9.20.1",
     "patch-package": "^8.0.0",
     "postcss-html": "^1.6.0",
-    "prettier": "^3.2.2",
-    "sass": "^1.69.7",
+    "prettier": "^3.2.4",
+    "sass": "^1.70.0",
     "sass-loader": "^13.3.3",
     "stylelint": "^15.11.0",
     "stylelint-config-recess-order": "^4.4.0",

+ 0 - 14
src/views/exercise_questions/answer/answer.js

@@ -1,14 +0,0 @@
-// 主观题列表
-export const subjectiveQuestionList = [
-  'write',
-  'repeat',
-  'chinese',
-  'talk_picture',
-  'read_aloud',
-  'word_card',
-  'answer_question',
-  'replace_answer',
-  'essay_question',
-  'table_fill',
-  'activity',
-];

+ 37 - 9
src/views/exercise_questions/answer/index.vue

@@ -63,7 +63,7 @@
       </template>
     </main>
 
-    <footer class="footer">
+    <footer class="footer" :style="{ justifyContent: isAnnotations ? 'space-between' : 'center' }">
       <el-popover v-model="isPopover" placement="top-start" trigger="click">
         <div v-if="isEnable(remark.is_remarked) && !isTeacher" class="remark-container">
           <div class="remark-info">
@@ -83,7 +83,13 @@
           <div class="title">增加批注</div>
           <div class="score">
             <span>分数</span>
-            <el-input-number v-model="remark.score" :min="0" :step="1" />
+            <el-input-number
+              v-model="remark.score"
+              :min="0"
+              :disabled="is_objective"
+              :max="question.score"
+              :step="question.score_type === scoreTypeList[0].value ? 1 : 0.1"
+            />
           </div>
           <el-input v-model="remark.remark" type="textarea" rows="6" resize="none" class="remark" />
           <div>图片/视频</div>
@@ -152,6 +158,14 @@
           >
         </template>
       </div>
+
+      <div v-if="isAnnotations" class="score_type">
+        本题分数:{{
+          question.score_type === scoreTypeList[0].value
+            ? `总分${question.score}分`
+            : `总分${question.score}分 每小题${question.score_item}分`
+        }}
+      </div>
     </footer>
   </div>
 </template>
@@ -170,9 +184,9 @@ import {
   GetQuestionInfo,
   EndAnswer,
 } from '@/api/exercise';
-import { subjectiveQuestionList } from './answer';
 import { fileUpload } from '@/api/app';
 import { exerciseNames } from '@/views/exercise_questions/data/questionType';
+import { scoreTypeList } from '@/views/exercise_questions/data/common';
 
 import StartQuestion from './components/StartQuestion.vue';
 import AnswerReport from './components/AnswerReport.vue';
@@ -211,6 +225,7 @@ export default {
       isTeacher: this.$store.getters.isTeacher, // 是否是教师
       user_answer_record_info: {}, // 当前用户的答题记录信息
       correct_answer_show_mode: 1,
+      scoreTypeList, // 分数类型列表
       // 问题列表
       questionList: [],
       // 当前问题
@@ -248,6 +263,12 @@ export default {
       }, // 答题报告
       exerciseNames,
       isShowQuestionList: false, // 是否显示题目列表
+      is_objective: false, // 是否客观题
+      question: {
+        score: 1,
+        score_item: 1,
+        score_type: 'aggregate',
+      }, // 题目信息
     };
   },
   computed: {
@@ -445,9 +466,9 @@ export default {
         answer_record_id: this.answer_record_id,
         question_id: this.questionList[this.curQuestionIndex].id,
         answer: JSON.stringify(this.$refs.exercise[0].answer),
-      }).then(() => {
+      }).then(({ user_answer }) => {
         this.questionList[this.curQuestionIndex].isFill = true;
-        if (subjectiveQuestionList.includes(this.currentQuestion.type) || this.isExamMode) {
+        if (!this.isEnable(user_answer.is_objective) || this.isExamMode) {
           if (type === 'pre') return this.preQuestion();
           if (type === 'next') return this.nextQuestion();
         }
@@ -473,10 +494,11 @@ export default {
       GetQuestionInfo_AnswerRecord({
         answer_record_id: this.answer_record_id,
         question_id: this.questionList[this.curQuestionIndex].id,
-      }).then(({ question, user_answer: { is_fill_answer, content }, remark }) => {
+      }).then(({ question, user_answer: { is_fill_answer, content, is_objective }, remark }) => {
         // 批注
         this.remark = remark;
 
+        this.question = question;
         // 题目内容
         if (question.content) {
           this.currentQuestion = JSON.parse(question.content);
@@ -486,6 +508,8 @@ export default {
               : this.previewComponents[this.questionList[this.curQuestionIndex].type];
         }
 
+        this.is_objective = this.isEnable(is_objective);
+
         // 如果已经填写过答案,直接显示答案
         if (is_fill_answer === 'true') {
           this.$nextTick().then(() => {
@@ -663,11 +687,9 @@ export default {
   .footer {
     position: relative;
     display: flex;
-    justify-content: center;
+    align-items: center;
 
     .annotations {
-      position: absolute;
-      left: 0;
       display: flex;
       column-gap: 8px;
       align-items: center;
@@ -692,6 +714,12 @@ export default {
     .el-button {
       padding: 9px 40px;
     }
+
+    .score_type {
+      font-size: 14px;
+      font-weight: bold;
+      color: $main-color;
+    }
   }
 }
 </style>

+ 63 - 0
src/views/exercise_questions/create/components/common/ImportPage.vue

@@ -0,0 +1,63 @@
+<template>
+  <el-dialog title="一键导入" :visible="visible" width="1000px" top="85px" :show-close="false" @close="dialogClose">
+    <el-input v-model="text" type="textarea" resize="none" placeholder="输入内容" />
+    <template slot="footer">
+      <el-button size="medium" @click="dialogClose">取消</el-button>
+      <el-button type="primary" size="medium" @click="confirm">确定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: 'ImportPage',
+  props: {
+    visible: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      text: '',
+    };
+  },
+  methods: {
+    dialogClose() {
+      this.$emit('update:visible', false);
+    },
+    confirm() {
+      this.$emit('update:visible', false);
+      this.$emit('oneKeyImport', this.text);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.el-dialog__wrapper {
+  :deep .el-dialog {
+    display: flex;
+    flex-direction: column;
+    height: 756px;
+
+    &__header {
+      padding: 24px 16px 0;
+    }
+
+    &__body {
+      flex: 1;
+      padding: 16px;
+
+      .el-textarea {
+        height: 100%;
+
+        textarea {
+          height: 100%;
+          color: #000;
+        }
+      }
+    }
+  }
+}
+</style>

+ 20 - 0
src/views/exercise_questions/create/components/common/QuestionMixin.js

@@ -27,6 +27,7 @@ const mixin = {
     };
   },
   provide: ['refreshPreviewData'],
+  inject: ['updateLoading'],
   props: {
     questionId: {
       type: String,
@@ -56,6 +57,7 @@ const mixin = {
         .then(({ question }) => {
           if (!question.content) return;
           this.data = JSON.parse(question.content);
+          this.$emit('loaded');
           this.refreshPreviewData();
         })
         .catch(() => {});
@@ -103,6 +105,9 @@ const mixin = {
      */
     setQuestion(content) {
       this.data = content;
+      if (!this.isChild) {
+        this.updateLoading(false);
+      }
     },
     /**
      * 单独设置题号
@@ -162,6 +167,21 @@ const mixin = {
       }
       return arr.slice(sliceLength);
     },
+    /**
+     * 智能识别公用设置属性
+     * @param {object} obj 对象
+     */
+    recognitionCommonSetObj(obj) {
+      Object.keys(obj).forEach((key) => {
+        key.split('.').reduce((prev, cur, index, arr) => {
+          if (index === arr.length - 1) {
+            prev[cur] = obj[key];
+          } else {
+            return prev[cur];
+          }
+        }, this);
+      });
+    },
   },
 };
 

+ 8 - 2
src/views/exercise_questions/create/components/create.vue

@@ -28,8 +28,14 @@
     </div>
     <div class="create-content">
       <QuestionHeader v-if="indexList.length > 0" :type="exerciseTypeList[indexList[curIndex].type]" />
-      <template v-for="({ id }, i) in indexList">
-        <component :is="curExercisePage" v-if="i === curIndex" :key="id" ref="exercise" :question-id="id" />
+      <template v-for="{ id } in indexList">
+        <component
+          :is="curExercisePage"
+          v-if="id === indexList[curIndex].id"
+          :key="id"
+          ref="exercise"
+          :question-id="id"
+        />
       </template>
     </div>
   </main>

+ 9 - 17
src/views/exercise_questions/create/components/exercises/ReadQuestion.vue

@@ -32,6 +32,7 @@
             :is-change.sync="change"
             :question-id="item.id"
             @updatePreviewData="updatePreviewData(i, $event)"
+            @loaded="loaded"
           />
         </KeepAlive>
       </div>
@@ -73,23 +74,6 @@
             {{ label }}
           </el-radio>
         </el-form-item>
-        <el-form-item label="分值">
-          <el-radio
-            v-for="{ value, label } in scoreTypeList"
-            :key="value"
-            v-model="data.property.score_type"
-            :label="value"
-          >
-            {{ label }}
-          </el-radio>
-        </el-form-item>
-        <el-form-item>
-          <el-input-number
-            v-model="data.property.score"
-            :min="0"
-            :step="data.property.score_type === scoreTypeList[0].value ? 1 : 0.1"
-          />
-        </el-form-item>
       </el-form>
     </template>
   </QuestionBase>
@@ -123,6 +107,7 @@ export default {
       questionTypeOption,
       exerciseTypeList,
       childPreviewData: [],
+      loaded_number: 0, // 已加载的题目数量
       data: JSON.parse(JSON.stringify(readData)),
       exerciseComponents: {
         select: SelectQuestion,
@@ -203,6 +188,13 @@ export default {
     updatePreviewData(i, data) {
       this.$set(this.childPreviewData, i, data);
     },
+
+    loaded() {
+      this.loaded_number += 1;
+      if (this.loaded_number === this.data.question_list.length) {
+        this.updateLoading(false);
+      }
+    },
   },
 };
 </script>

+ 8 - 2
src/views/exercise_questions/create/components/exercises/SelectQuestion.vue

@@ -153,7 +153,12 @@
 import QuestionMixin from '../common/QuestionMixin.js';
 
 import { selectTypeList, scoreTypeList, changeOptionType, isEnable } from '@/views/exercise_questions/data/common';
-import { getSelectData, getOption, getSubdivisionOption } from '@/views/exercise_questions/data/select';
+import {
+  getSelectData,
+  getOption,
+  getSubdivisionOption,
+  analysisRecognitionData,
+} from '@/views/exercise_questions/data/select';
 
 export default {
   name: 'SelectQuestion',
@@ -180,7 +185,8 @@ export default {
      */
     recognition(text) {
       let arr = this.recognitionCommon(text);
-      this.data.option_list = arr.map((content) => getOption(content));
+      let obj = analysisRecognitionData(arr);
+      this.recognitionCommonSetObj(obj);
       this.data.answer.answer_list = [];
     },
     changeSelectType(val) {

+ 26 - 5
src/views/exercise_questions/create/index.vue

@@ -28,7 +28,7 @@
         </ul>
       </div>
       <div class="list-operate">
-        <!-- <el-button type="primary">批量导入</el-button> -->
+        <!-- <el-button type="primary" @click="oneClickImport">一键导入</el-button> -->
         <el-button type="primary" @click="showSelectQuestionType">新建</el-button>
       </div>
     </div>
@@ -67,11 +67,12 @@
     </div>
 
     <SelectQuestionType :visible.sync="visible" @addQuestionToExercise="addQuestionToExercise" />
+    <ImportPage :visible.sync="importVisible" @oneKeyImport="oneKeyImport" />
   </div>
 </template>
 
 <script>
-import { exerciseNames, questionDataList } from '../data/questionType';
+import { exerciseNames, questionDataList, analysisOneKeyImportData } from '../data/questionType';
 import {
   AddQuestionToExercise,
   DeleteQuestion,
@@ -84,6 +85,7 @@ import {
 
 import CreateMain from './components/create.vue';
 import PreviewQuestionTypeMixin from '../data/PreviewQuestionTypeMixin';
+import ImportPage from './components/common/ImportPage.vue';
 import SelectQuestionType from './components/common/SelectQuestionType.vue';
 import draggable from 'vuedraggable';
 
@@ -93,6 +95,7 @@ export default {
     CreateMain,
     SelectQuestionType,
     draggable,
+    ImportPage,
   },
   mixins: [PreviewQuestionTypeMixin],
   provide() {
@@ -100,6 +103,9 @@ export default {
       updateCurQuestionType: this.updateCurQuestionType,
       exercise_id: this.exercise_id,
       refreshPreviewData: this.refreshPreviewData,
+      updateLoading: (loading) => {
+        this.loading = loading;
+      },
     };
   },
   data() {
@@ -119,6 +125,7 @@ export default {
       previewData: {}, // 预览数据
       back_url: back_url || '/personal_question', // 返回地址
       visible: false, // 选择题目类型弹窗
+      importVisible: false, // 一键导入弹窗
     };
   },
   computed: {
@@ -149,6 +156,7 @@ export default {
      * @param {string} content 题目内容
      */
     addQuestionToExercise(type, additional_type, content = '') {
+      this.$refs.createMain.saveQuestion();
       AddQuestionToExercise({
         exercise_id: this.exercise_id,
         type,
@@ -165,6 +173,14 @@ export default {
     showSelectQuestionType() {
       this.visible = true;
     },
+    oneClickImport() {
+      this.importVisible = true;
+    },
+    oneKeyImport(text) {
+      let questionList = text.split(/\n\s*\n+/);
+      console.log(questionList);
+      analysisOneKeyImportData(questionList);
+    },
     // 创建默认题目
     createDefaultQuestion() {
       this.addQuestionToExercise('select', 'single', questionDataList['select']);
@@ -216,10 +232,10 @@ export default {
       if (this.index_list.length === 0) return;
       this.$refs.createMain.clearSaveDate();
       this.loading = true;
-      let curI = this.curIndex;
+      let curId = this.index_list[this.curIndex].id;
       GetQuestionInfo({ question_id: this.index_list[this.curIndex].id })
         .then(({ question, file_list }) => {
-          if (curI !== this.curIndex) return;
+          if (curId !== this.index_list[this.curIndex].id) return;
           this.$refs.createMain.resetSaveDate();
           if (!question.content) return;
           // 将题目文件id列表添加到题目内容中
@@ -230,7 +246,6 @@ export default {
           this.$nextTick(() => {
             this.$refs.createMain.$refs.exercise?.[0].setQuestion(content);
             this.refreshPreviewData();
-            this.loading = false;
           });
         })
         .catch(() => {});
@@ -318,6 +333,11 @@ export default {
      * @param {number} param.oldIndex 移动前的索引
      */
     moveQuestion({ newIndex, oldIndex }) {
+      if (newIndex === oldIndex) {
+        this.isMoveQuestion = false;
+        this.moveQuestionData = {};
+        return;
+      }
       let isMoveCur = newIndex === this.curIndex || oldIndex === this.curIndex; // 是否移动当前题目
       let isEffectCurIndex = newIndex < this.curIndex !== oldIndex < this.curIndex; // 是否影响当前题目索引
       if (isMoveCur) {
@@ -329,6 +349,7 @@ export default {
       if (isMoveCur || isEffectCurIndex) {
         this.$nextTick(() => {
           this.$refs.createMain.$refs.exercise?.[0].setQuestion(this.moveQuestionData);
+          this.moveQuestionData = {};
           this.refreshPreviewData();
           this.$refs.createMain.resetSaveDate();
         });

+ 20 - 0
src/views/exercise_questions/data/questionType.js

@@ -145,3 +145,23 @@ export function getExerciseTypeList(arr) {
 
 // 题型类型列表
 export const exerciseTypeList = getExerciseTypeList(questionTypeOption);
+
+/**
+ * 解析一键导入数据
+ * @param {Array} questionList 题目列表
+ */
+export function analysisOneKeyImportData(questionList) {
+  questionList.forEach((item) => {
+    let arr = item
+      .split(/[\r\n]/)
+      .map((item) => item.trim())
+      .filter((item) => item);
+    if (arr.length === 0) return;
+    let [question_number, type_name] = arr[0].split('.');
+    let type = Object.entries(exerciseNames).find(([key, value]) => {
+      return value === type_name.trim();
+    })?.[0];
+
+    if (!type) return;
+  });
+}

+ 0 - 2
src/views/exercise_questions/data/read.js

@@ -28,8 +28,6 @@ export const readData = {
     question_number: '1', // 题号
     stem_question_number_font_size: fontSizeList[6], // 题干题号
     is_enable_description: switchOption[1].value, // 描述
-    score: 1, // 分值
-    score_type: scoreTypeList[0].value, // 分值类型
   },
   // 其他属性
   other: {

+ 11 - 0
src/views/exercise_questions/data/select.js

@@ -25,6 +25,17 @@ export function getSubdivisionOption(number = 2) {
 }
 
 /**
+ * 解析智能识别数据
+ * @param {array} arr 智能识别数据
+ * @returns object
+ */
+export function analysisRecognitionData(arr) {
+  return {
+    'data.option_list': arr.map((content) => getOption(content)),
+  };
+}
+
+/**
  * 获取选择题数据模板(防止 mark 重复)
  */
 export function getSelectData() {

+ 1 - 0
src/views/home/recovery/AnswerRecordList.vue

@@ -272,6 +272,7 @@ export default {
         flex: 1;
         flex-wrap: wrap;
         gap: 24px;
+        align-content: flex-start;
         width: 770px;
 
         &-item {