瀏覽代碼

对话题格式更新

dusenyao 1 年之前
父節點
當前提交
4b1c89b397

+ 57 - 9
src/views/exercise_questions/create/components/exercises/DialogueQuestion.vue

@@ -4,24 +4,31 @@
       <div class="stem">
         <RichText v-model="data.stem" :font-size="18" placeholder="输入题干" />
 
-        <el-input
+        <RichText
           v-if="isEnable(data.property.is_enable_description)"
           v-model="data.description"
-          rows="3"
-          resize="none"
-          type="textarea"
-          placeholder="输入填空内容"
+          placeholder="输入提示"
         />
       </div>
 
       <div class="content-wrapper">
         <div class="content">
-          <div v-for="({ role, type, text, file_id }, i) in data.option_list" :key="i" class="option-list">
+          <div
+            v-for="({ role, type, text, file_id }, i) in data.option_list"
+            :key="i"
+            class="option-list"
+            :style="{ flexDirection: type === 'input_student' ? 'row-reverse' : 'row' }"
+          >
             <span
               class="avatar"
-              :style="{ backgroundColor: data.property.role_list.find((item) => item.mark === role).color }"
+              :style="{
+                backgroundColor:
+                  type === 'input_student'
+                    ? '#306EFF'
+                    : data.property.role_list.find((item) => item.mark === role).color,
+              }"
             >
-              {{ data.property.role_list.find((item) => item.mark === role).name }}
+              {{ type === 'input_student' ? '学生' : data.property.role_list.find((item) => item.mark === role).name }}
             </span>
 
             <div v-if="type === 'text' || type === 'input'" class="text">{{ text }}</div>
@@ -39,7 +46,11 @@
               />
             </div>
 
-            <div class="content-operation">
+            <div v-else-if="type === 'input_student'">
+              <SoundRecordPreview type="small" :disabled="true" />
+            </div>
+
+            <div class="content-operation" :style="{ flexDirection: type === 'input_student' ? 'row-reverse' : 'row' }">
               <div class="up-down">
                 <span :style="{ borderBottomColor: i === 0 ? '#c2c2c4' : '#000' }" @click="moveOption('up', i)"></span>
                 <span
@@ -85,6 +96,10 @@
               <SvgIcon icon-class="picture" />
               <span>上传图片</span>
             </el-upload>
+            <div class="insert-student" @click="insertStudent">
+              <SvgIcon icon-class="add-circle" size="14" />
+              <span>插入学生</span>
+            </div>
           </div>
         </div>
 
@@ -178,6 +193,7 @@
 import QuestionMixin from '../common/QuestionMixin.js';
 import AudioPlay from '../common/AudioPlay.vue';
 import SoundRecord from '@/components/common/SoundRecord.vue';
+import SoundRecordPreview from '@/views/exercise_questions/preview/components/common/SoundRecordPreview.vue';
 
 import { getRandomNumber } from '@/utils';
 import { fileUpload, GetFileURLMap } from '@/api/app';
@@ -188,6 +204,7 @@ export default {
   components: {
     AudioPlay,
     SoundRecord,
+    SoundRecordPreview,
   },
   mixins: [QuestionMixin],
   data() {
@@ -368,6 +385,14 @@ export default {
         this.identifyText();
       }
     },
+    // 插入学生
+    insertStudent() {
+      this.data.option_list.push({
+        mark: getRandomNumber(),
+        file_id: '',
+        type: 'input_student',
+      });
+    },
     /**
      * 保存音频
      * @param {string} file_id 文件id
@@ -452,6 +477,22 @@ export default {
         }
       }
 
+      .sound-record-preview {
+        padding: 4px;
+        background-color: #fff;
+        border-radius: 40px;
+
+        :deep .sound-item .sound-item-span {
+          color: #1d1d1d;
+          background-color: #fff;
+        }
+
+        :deep .sound-item-luyin .sound-item-span {
+          color: #fff;
+          background-color: $light-main-color;
+        }
+      }
+
       .text {
         padding: 8px 12px;
         word-break: break-all;
@@ -486,6 +527,13 @@ export default {
       align-items: center;
       color: $main-color;
 
+      .insert-student {
+        display: flex;
+        column-gap: 8px;
+        align-items: center;
+        cursor: pointer;
+      }
+
       :deep .el-upload {
         display: flex;
         column-gap: 8px;

+ 1 - 1
src/views/exercise_questions/create/index.vue

@@ -320,7 +320,7 @@ export default {
 <style lang="scss" scoped>
 .exercise {
   display: grid;
-  grid-template: 1fr auto / 224px 1fr;
+  grid-template: 1fr auto / 224px minmax(800px, 1fr);
   height: 100%;
 
   .list {

+ 47 - 73
src/views/exercise_questions/preview/DialoguePreview.vue

@@ -5,19 +5,33 @@
       <span class="question-number">{{ data.property.question_number }}.</span>
       <span v-html="sanitizeHTML(data.stem)"></span>
     </div>
-    <div v-if="isEnable(data.property.is_enable_description)" class="description">
-      <span v-for="(text, i) in descriptionList" :key="i" class="description-item" @click="selectedDescription(text)">
-        {{ text }}
-      </span>
-    </div>
+    <div
+      v-if="isEnable(data.property.is_enable_description)"
+      class="description rich-text"
+      v-html="sanitizeHTML(data.description)"
+    ></div>
 
     <div class="content">
-      <div v-for="(item, i) in optionList" :key="i" class="option-list">
+      <div
+        v-for="(item, i) in optionList"
+        :key="i"
+        class="option-list"
+        :style="{ flexDirection: item.type === 'input_student' ? 'row-reverse' : 'row' }"
+      >
         <span
           class="avatar"
-          :style="{ backgroundColor: data.property.role_list?.find(({ mark }) => mark === item.role).color }"
+          :style="{
+            backgroundColor:
+              item.type === 'input_student'
+                ? '#306EFF'
+                : data.property.role_list?.find(({ mark }) => mark === item.role).color,
+          }"
         >
-          {{ data.property.role_list?.find(({ mark }) => mark === item.role).name }}
+          {{
+            item.type === 'input_student'
+              ? '学生'
+              : data.property.role_list?.find(({ mark }) => mark === item.role).name
+          }}
         </span>
 
         <div v-if="item.type === 'text' || item.type === 'input'" class="text-wrapper">
@@ -31,7 +45,6 @@
                   :disabled="disabled"
                   :class="[...computedAnswerClass(item.mark, li.mark, li.content)]"
                   :style="[{ width: Math.max(80, li.content.length * 16) + 'px' }]"
-                  @focus="handleInputFocus(i, j)"
                 />
                 <span
                   v-show="computedAnswerText(item.mark, li.mark, li.content).length > 0"
@@ -63,6 +76,10 @@
             :background-color="data.property.role_list?.find(({ mark }) => mark === item.role).color"
           />
         </div>
+
+        <div v-else-if="item.type === 'input_student'">
+          <SoundRecordPreview :wav-blob.sync="item.file_id" :disabled="disabled" type="small" />
+        </div>
       </div>
     </div>
   </div>
@@ -91,11 +108,6 @@ export default {
       },
     };
   },
-  computed: {
-    descriptionList() {
-      return this.data.description.split(/\s+/).filter((item) => item);
-    },
-  },
   watch: {
     'data.option_list': {
       handler(val) {
@@ -118,6 +130,14 @@ export default {
       handler(val) {
         this.answer.answer_list = [];
         val.forEach(({ type, content_list, mark, file_id }) => {
+          if (type === 'input_student') {
+            if (file_id.length <= 0) return;
+            this.answer.answer_list.push({
+              audio_file_id: file_id,
+              mark,
+            });
+            return;
+          }
           if (type !== 'input') return;
           let list = content_list
             .map(({ type, mark, content }) => {
@@ -144,7 +164,7 @@ export default {
       this.answer.answer_list.forEach(({ mark, audio_file_id, content_list }) => {
         let findOption = this.optionList.find((item) => item.mark === mark);
         findOption.file_id = audio_file_id;
-        findOption.content_list.forEach((item) => {
+        findOption?.content_list.forEach((item) => {
           if (item.type === 'text') return;
           let find = content_list.find((li) => li.mark === item.mark);
           item.content = find.value;
@@ -152,54 +172,8 @@ export default {
       });
     },
   },
-  created() {
-    document.addEventListener('click', this.handleBlur);
-    document.addEventListener('keydown', this.handleBlurTab);
-  },
-  beforeDestroy() {
-    document.removeEventListener('click', this.handleBlur);
-    document.removeEventListener('keydown', this.handleBlurTab);
-  },
   methods: {
     /**
-     * 处理点击失焦
-     * @param {MouseEvent} e
-     */
-    handleBlur(e) {
-      if (e.target.tagName === 'INPUT') return;
-      this.inputFocus = false;
-    },
-    /**
-     * 处理 tab 键失焦
-     * @param {KeyboardEvent} e
-     */
-    handleBlurTab(e) {
-      if (e.key !== 'Tab') return;
-      this.inputFocus = false;
-    },
-    /**
-     * 处理输入框聚焦
-     * @param {number} i
-     * @param {number} j
-     */
-    handleInputFocus(i, j) {
-      this.inputFocus = true;
-      this.focusPostion = {
-        i,
-        j,
-      };
-    },
-    /**
-     * 选中描述
-     * @param {string} text 描述文本
-     */
-    selectedDescription(text) {
-      if (!this.inputFocus) return;
-      const { i, j } = this.focusPostion;
-      this.optionList[i].content_list[j].content = text;
-      this.inputFocus = false;
-    },
-    /**
      * 计算答题对错选项字体颜色
      * @param {string} optionMark 选项标识
      * @param {string} mark 选项标识
@@ -348,21 +322,21 @@ export default {
             }
           }
         }
+      }
 
-        .sound-record-preview {
-          padding: 4px;
-          background-color: #fff;
-          border-radius: 40px;
+      .sound-record-preview {
+        padding: 4px;
+        background-color: #fff;
+        border-radius: 40px;
 
-          :deep .sound-item .sound-item-span {
-            color: #1d1d1d;
-            background-color: #fff;
-          }
+        :deep .sound-item .sound-item-span {
+          color: #1d1d1d;
+          background-color: #fff;
+        }
 
-          :deep .sound-item-luyin .sound-item-span {
-            color: #fff;
-            background-color: $light-main-color;
-          }
+        :deep .sound-item-luyin .sound-item-span {
+          color: #fff;
+          background-color: $light-main-color;
         }
       }
 

+ 1 - 1
src/views/exercise_questions/preview/FillPreview.vue

@@ -21,7 +21,7 @@
               v-model="li.content"
               :disabled="disabled"
               :class="[...computedAnswerClass(li.mark)]"
-              :style="[{ width: Math.max(80, li.content.length * 16) + 'px' }]"
+              :style="[{ width: Math.max(80, li.content.length * 21.3) + 'px' }]"
               @focus="handleInputFocus(i, j)"
               @blur="handleTone(li.content, i, j)"
             />

+ 1 - 1
src/views/exercise_questions/preview/ListenFillPreview.vue

@@ -28,7 +28,7 @@
               v-model="li.content"
               :disabled="disabled"
               :class="[...computedAnswerClass(li.mark)]"
-              :style="[{ width: Math.max(80, li.content.length * 16) + 'px' }]"
+              :style="[{ width: Math.max(80, li.content.length * 21.3) + 'px' }]"
               @focus="handleInputFocus(i, j)"
               @blur="handleTone(li.content, i, j)"
             />