소스 검색

Merge branch 'master' of http://60.205.254.193:3000/GCLS/GCLS_Page_Exercise

dusenyao 1 년 전
부모
커밋
1c7df3a252

+ 4 - 0
src/components/common/RichText.vue

@@ -8,6 +8,7 @@
     :class="['rich-text', isBorder ? 'is-border' : '']"
     :init="init"
     v-on="$listeners"
+    @onBlur="handleRichTextBlur"
   />
 </template>
 
@@ -314,6 +315,9 @@ export default {
     replaceSpanString(str) {
       return str.replace(/<span\b[^>]*>(.*?)<\/span>/gi, '$1');
     },
+    handleRichTextBlur() {
+      this.$emit('handleRichTextBlur');
+    },
   },
 };
 </script>

+ 53 - 13
src/views/exercise_questions/create/components/exercises/ChineseQuestion.vue

@@ -154,7 +154,7 @@ import QuestionMixin from '../common/QuestionMixin.js';
 import UploadAudio from '../common/UploadAudio.vue';
 import SoundRecord from '../common/SoundRecord.vue';
 import { GetStaticResources } from '@/api/app';
-import { changeOptionType, handleInputNumber } from '@/views/exercise_questions/data/common';
+import { changeOptionType, handleInputNumber, addTone } from '@/views/exercise_questions/data/common';
 import { getRandomNumber } from '@/utils/index';
 
 import {
@@ -192,6 +192,7 @@ export default {
           loadings: false,
         },
       ],
+      matically_pinyin_obj: {}, // 存放转成声调的拼音
     };
   },
   watch: {
@@ -210,18 +211,7 @@ export default {
       immediate: true,
     },
   },
-  mounted() {
-    // if (this.data.option_list.length > 3) {
-    //   let length = this.data.option_list.length - 3;
-    //   for (let i = 0; i < length; i++) {
-    //     let obj = {
-    //       loading: false,
-    //       loadings: false,
-    //     };
-    //     this.loading_list.push(obj);
-    //   }
-    // }
-  },
+  mounted() {},
   methods: {
     addOption() {
       this.data.option_list.push(getOption());
@@ -306,6 +296,13 @@ export default {
     },
     // 切割拼音
     handleSplitPy(item) {
+      let index = item.pinyin.search(/0|1|2|3|4/);
+      if (index > -1) {
+        this.handleItemPinyin(item.pinyin, item.mark);
+        setTimeout(() => {
+          item.pinyin = this.matically_pinyin_obj[item.mark];
+        }, 100);
+      }
       let pinyin_list = item.pinyin.trim().split(' ');
       item.pinyin_item_list = [];
       pinyin_list.forEach((itemp) => {
@@ -315,6 +312,49 @@ export default {
         item.pinyin_item_list.push(obj);
       });
     },
+    handleReplaceTone(value, mark) {
+      if (!value) return;
+      value.split(/\s+/).forEach((item) => {
+        this.handleValue(item);
+      });
+      this.matically_pinyin_obj[mark] = this.res_arr
+        .map((item) =>
+          item.map(({ number, con }) => (number && con ? addTone(Number(number), con) : number || con || '')),
+        )
+        .filter((item) => item.length > 0)
+        .join(' ');
+    },
+    handleValue(valItem) {
+      let numList = [];
+      if (/[A-Za-zü]+\d/g.test(valItem)) {
+        valItem.split('').forEach((item, i) => {
+          if (/\d/.test(item)) {
+            let con = valItem.replace(/\d/g, '');
+            numList.push({
+              index: i,
+              number: item,
+              con,
+              isTran: true,
+            });
+          }
+        });
+      } else {
+        numList = [];
+      }
+
+      this.res_arr.push(numList.length === 0 ? [{ con: valItem }] : numList);
+    },
+    handleItemPinyin(content, mark) {
+      let content_arr = content.trim().split(' ');
+      this.res_arr = [];
+      this.$set(this.matically_pinyin_obj, mark, []);
+      content_arr.forEach((items, index) => {
+        let items_trim = items.trim();
+        if (items_trim) {
+          this.handleReplaceTone(items_trim, mark);
+        }
+      });
+    },
     // 修改拼音
     changePinyin(item) {
       if (this.data.other.audio_generation_method === 'auto') {

+ 6 - 1
src/views/exercise_questions/create/components/exercises/ChooseToneQuestion.vue

@@ -270,7 +270,9 @@ export default {
     handleItemAnswer(item) {
       const index = this.data.answer.answer_list.findIndex((items) => items.mark === item.mark);
       let content = item.content.trim();
-      let content_arr = content.split(' ');
+      const regex = /[\u4e00-\u9fa5]/g;
+      item.content_hz = content.match(regex) ? content.match(regex).join('') : '';
+      let content_arr = content.replace(regex, '').trim().split(' ');
       let select_item = '';
       let content_preview = '';
       this.res_arr = [];
@@ -329,6 +331,9 @@ export default {
       if (arr.length > 0) {
         this.data.stem = arr[0];
         this.data.option_list = arr.slice(1).map((content) => getOption(content));
+        this.data.option_list.forEach((item) => {
+          this.handleItemAnswer(item);
+        });
       }
     },
   },

+ 74 - 6
src/views/exercise_questions/create/components/exercises/RepeatQuestion.vue

@@ -20,7 +20,13 @@
               {{ computedQuestionNumber(i, data.option_number_show_mode) }}
             </span>
             <div class="option-content">
-              <RichText v-model="item.content" :class="'repeat' + i" placeholder="输入内容" :inline="true" />
+              <RichText
+                v-model="item.content"
+                class="repeat-richtext"
+                placeholder="输入内容"
+                :inline="true"
+                @handleRichTextBlur="handleRichTextBlur"
+              />
             </div>
             <UploadAudio
               v-if="data.other.audio_generation_method === 'upload'"
@@ -115,7 +121,7 @@ import UploadAudio from '../common/UploadAudio.vue';
 import QuestionMixin from '../common/QuestionMixin.js';
 import SoundRecord from '../common/SoundRecord.vue';
 
-import { selectTypeList, changeOptionType } from '@/views/exercise_questions/data/common';
+import { selectTypeList, changeOptionType, addTone } from '@/views/exercise_questions/data/common';
 import { repeatData, getOption, audioGenerationMethodList } from '@/views/exercise_questions/data/repeat';
 import { GetStaticResources } from '@/api/app';
 
@@ -143,6 +149,7 @@ export default {
           loading: false,
         },
       ],
+      matically_pinyin_obj: {},
     };
   },
   watch: {
@@ -201,17 +208,78 @@ export default {
         })
         .catch(() => {});
     },
+    handleReplaceTone(value, mark) {
+      if (!value) return;
+      value.split(/\s+/).forEach((item) => {
+        this.handleValue(item);
+      });
+      this.matically_pinyin_obj[mark] = this.res_arr
+        .map((item) =>
+          item.map(({ number, con }) => (number && con ? addTone(Number(number), con) : number || con || '')),
+        )
+        .filter((item) => item.length > 0)
+        .join(' ');
+    },
+    handleValue(valItem) {
+      let numList = [];
+      if (/[A-Za-zü]+\d/g.test(valItem)) {
+        valItem.split('').forEach((item, i) => {
+          if (/\d/.test(item)) {
+            let con = valItem.replace(/\d/g, '');
+            numList.push({
+              index: i,
+              number: item,
+              con,
+              isTran: true,
+            });
+          }
+        });
+      } else {
+        numList = [];
+      }
+
+      this.res_arr.push(numList.length === 0 ? [{ con: valItem }] : numList);
+    },
+    handleItemPinyin(content, mark) {
+      let content_arr = content.trim().split(' ');
+      this.res_arr = [];
+      this.$set(this.matically_pinyin_obj, mark, []);
+      content_arr.forEach((items, index) => {
+        let items_trim = items.trim();
+        if (items_trim) {
+          this.handleReplaceTone(items_trim, mark);
+        }
+      });
+    },
+    // 转成带声调的拼音
+    handleRichTextBlur() {
+      let rich_rext_arr = document.getElementsByClassName('repeat-richtext');
+      if (rich_rext_arr) {
+        for (let i = 0; i < rich_rext_arr.length; i++) {
+          let content = rich_rext_arr[i].innerText;
+          let index = content.search(/0|1|2|3|4/);
+          if (index > -1) {
+            this.handleItemPinyin(content, this.data.option_list[i].mark);
+            setTimeout(() => {
+              document.getElementsByClassName('repeat-richtext')[i].innerText =
+                this.matically_pinyin_obj[this.data.option_list[i].mark];
+            }, 100);
+          }
+        }
+      }
+    },
     // 自动生成音频
     handleMatically(item, i) {
       if (
-        document.getElementsByClassName(`repeat${i}`) &&
-        document.getElementsByClassName(`repeat${i}`)[0] &&
-        document.getElementsByClassName(`repeat${i}`)[0].innerText
+        document.getElementsByClassName('repeat-richtext') &&
+        document.getElementsByClassName('repeat-richtext')[i] &&
+        document.getElementsByClassName('repeat-richtext')[i].innerText
       ) {
         this.loading_list[i].loading = true;
+
         let MethodName = 'tool-PinyinToVoiceFile';
         let data = {
-          pinyin: document.getElementsByClassName(`repeat${i}`)[0].innerText.trim().split(' ').join(','),
+          pinyin: document.getElementsByClassName('repeat-richtext')[i].innerText.trim().split(' ').join(','),
         };
         GetStaticResources(MethodName, data)
           .then((res) => {

+ 16 - 0
src/views/exercise_questions/create/components/exercises/TalkPictureQuestion.vue

@@ -14,6 +14,9 @@
       <div class="content">
         <div v-for="(item, index) in data.option_list" :key="index" class="content-item">
           <template v-if="pic_list[item.picture_file_id]">
+            <span class="question-number" title="双击切换序号类型" @dblclick="changeOptionType(data)">
+              {{ computedQuestionNumber(index, data.option_number_show_mode) }}
+            </span>
             <div class="item-left">
               <el-image
                 style="width: 72px; height: 72px"
@@ -115,6 +118,7 @@ import QuestionMixin from '../common/QuestionMixin.js';
 import { talkPictrueData, getOption } from '@/views/exercise_questions/data/talkPicture';
 import { GetFileStoreInfo } from '@/api/app';
 import UploadDrag from '../common/UploadDrag.vue';
+import { changeOptionType } from '@/views/exercise_questions/data/common';
 
 export default {
   name: 'TalkPicture',
@@ -122,6 +126,7 @@ export default {
   mixins: [QuestionMixin],
   data() {
     return {
+      changeOptionType,
       data: JSON.parse(JSON.stringify(talkPictrueData)),
       pic_list: {},
       is_first: true,
@@ -261,5 +266,16 @@ export default {
       }
     }
   }
+
+  .question-number {
+    min-width: 40px;
+    height: 32px;
+    padding: 4px 0;
+    color: $text-color;
+    text-align: center;
+    cursor: default;
+    background-color: $fill-color;
+    border-radius: 2px;
+  }
 }
 </style>

+ 52 - 1
src/views/exercise_questions/create/components/exercises/WordCardQuestion.vue

@@ -148,7 +148,7 @@ import QuestionMixin from '../common/QuestionMixin.js';
 import UploadAudio from '../common/UploadAudio.vue';
 import SoundRecord from '../common/SoundRecord.vue';
 import { GetStaticResources, GetFileStoreInfo } from '@/api/app';
-import { changeOptionType, handleInputNumber } from '@/views/exercise_questions/data/common';
+import { changeOptionType, handleInputNumber, addTone } from '@/views/exercise_questions/data/common';
 import UploadDrag from '../common/UploadDrag.vue';
 
 import {
@@ -189,6 +189,7 @@ export default {
           loadings: false,
         },
       ],
+      matically_pinyin_obj: {},
     };
   },
   watch: {
@@ -339,11 +340,61 @@ export default {
     addSentence(item) {
       item.example_sentence.push('');
     },
+    handleReplaceTone(value, mark) {
+      if (!value) return;
+      value.split(/\s+/).forEach((item) => {
+        this.handleValue(item);
+      });
+      this.matically_pinyin_obj[mark] = this.res_arr
+        .map((item) =>
+          item.map(({ number, con }) => (number && con ? addTone(Number(number), con) : number || con || '')),
+        )
+        .filter((item) => item.length > 0)
+        .join(' ');
+    },
+    handleValue(valItem) {
+      let numList = [];
+      if (/[A-Za-zü]+\d/g.test(valItem)) {
+        valItem.split('').forEach((item, i) => {
+          if (/\d/.test(item)) {
+            let con = valItem.replace(/\d/g, '');
+            numList.push({
+              index: i,
+              number: item,
+              con,
+              isTran: true,
+            });
+          }
+        });
+      } else {
+        numList = [];
+      }
+
+      this.res_arr.push(numList.length === 0 ? [{ con: valItem }] : numList);
+    },
+    handleItemPinyin(content, mark) {
+      let content_arr = content.trim().split(' ');
+      this.res_arr = [];
+      this.$set(this.matically_pinyin_obj, mark, []);
+      content_arr.forEach((items, index) => {
+        let items_trim = items.trim();
+        if (items_trim) {
+          this.handleReplaceTone(items_trim, mark);
+        }
+      });
+    },
     // 修改拼音
     changePinyin(item) {
       if (this.data.other.audio_generation_method === 'auto') {
         item.audio_file_id = '';
       }
+      let index = item.pinyin.search(/0|1|2|3|4/);
+      if (index > -1) {
+        this.handleItemPinyin(item.pinyin, item.mark);
+        setTimeout(() => {
+          item.pinyin = this.matically_pinyin_obj[item.mark];
+        }, 100);
+      }
     },
     /**
      * 智能识别

+ 1 - 0
src/views/exercise_questions/data/chooseTone.js

@@ -7,6 +7,7 @@ export function getOption(content = '') {
     mark: getRandomNumber(),
     content_view: [],
     audio_file_id: '',
+    content_hz: '',
   };
 }
 

+ 2 - 1
src/views/exercise_questions/data/talkPicture.js

@@ -1,4 +1,4 @@
-import { stemTypeList, scoreTypeList, questionNumberTypeList, switchOption } from './common';
+import { stemTypeList, scoreTypeList, questionNumberTypeList, switchOption, optionTypeList } from './common';
 import { getRandomNumber } from '@/utils/index';
 export function getOption() {
   return { picture_info: '', reference_answer: '', picture_file_id: '', mark: getRandomNumber() };
@@ -11,6 +11,7 @@ export const talkPictrueData = {
   option_list: [], // 选项
   file_id_list: [],
   answer: { score: 1, score_type: scoreTypeList[0].value }, // 答案
+  option_number_show_mode: optionTypeList[0].value, // 选项类型
   // 题型属性
   property: {
     stem_type: stemTypeList[1].value, // 题干类型

+ 15 - 8
src/views/exercise_questions/preview/ChooseTonePreview.vue

@@ -13,11 +13,12 @@
     <div class="option-list">
       <li v-for="(item, i) in data.option_list" :key="i" :class="['option-item']">
         <span>{{ computeOptionMethods[data.option_number_show_mode](i) }} </span>
-        <AudioPlay v-if="item.audio_file_id" :file-id="item.audio_file_id" />
+        <AudioPlay v-if="item.audio_file_id" :file-id="item.audio_file_id" :showSlider="true" />
         <div
           class="option-content"
           :class="[isJudgingRightWrong ? (con_preview[i].all_right ? 'all-right' : 'has-error') : '']"
         >
+          <span class="items-hz" v-if="item.content_hz">{{ item.content_hz }}</span>
           <template v-if="data.property.answer_mode === 'select'">
             <span
               v-for="(itemc, indexc) in con_preview[i].item_con"
@@ -81,7 +82,8 @@
               con_preview[i].user_answer[con_preview[i].item_active_index].select_index_submit !==
                 con_preview[i].user_answer[con_preview[i].item_active_index].right_answer &&
               data.property.answer_mode === 'select') ||
-            (data.property.answer_mode === 'label' &&
+            (isJudgingRightWrong &&
+              data.property.answer_mode === 'label' &&
               con_preview[i].user_answer[con_preview[i].item_active_index].right_answer === value &&
               con_preview[i].user_answer[con_preview[i].item_active_index].right_index ===
                 con_preview[i].user_answer[con_preview[i].item_active_index].select_index &&
@@ -448,16 +450,12 @@ export default {
   min-height: 450px;
 
   .option-list {
-    display: flex;
-    flex-wrap: wrap;
-    row-gap: 16px;
-
     .option-item {
       display: flex;
       column-gap: 16px;
       align-items: center;
-      width: 45%;
-      margin-right: 5%;
+      width: 100%;
+      margin-bottom: 16px;
 
       .option-content {
         padding: 10px 22px;
@@ -476,9 +474,18 @@ export default {
         }
       }
 
+      .items-hz {
+        margin-right: 4px;
+        font-size: 16px;
+        font-weight: 500;
+        line-height: 24px;
+        color: #000;
+      }
+
       .item-con,
       .items-con {
         font-family: 'League';
+        font-weight: 500;
         color: #000;
         cursor: pointer;
 

+ 1 - 0
src/views/exercise_questions/preview/RepeatPreview.vue

@@ -16,6 +16,7 @@
         <AudioPlay
           v-if="data.option_list[i] && data.option_list[i].audio_file_id"
           :file-id="data.option_list[i].audio_file_id"
+          :showSlider="true"
         />
         <div
           v-if="sanitizeHTML(data.option_list[i].content)"

+ 30 - 10
src/views/exercise_questions/preview/TalkPictruePreview.vue

@@ -16,16 +16,24 @@
           v-if="pic_list[item.picture_file_id]"
           style="width: 370px; height: 238px"
           :src="pic_list[item.picture_file_id]"
-          fit="contain"
+          fit="cover"
         />
-        <p class="pic-info rich-text" v-html="sanitizeHTML(item.picture_info)"></p>
-        <!-- 语音作答 -->
-        <div v-if="isEnable(data.property.is_enable_voice_answer) && answer.answer_list[index]" class="sound-box">
-          <SoundRecordPreview
-            :disabled="disabled"
-            :wav-blob.sync="answer.answer_list[index].audio_file_id"
-            type="small"
-          />
+        <div class="content-box" v-if="item.picture_info">
+          <span class="option-number">{{ computeOptionMethods[data.option_number_show_mode](index) }} </span>
+          <p class="pic-info rich-text" v-html="sanitizeHTML(item.picture_info)"></p>
+        </div>
+        <div class="content-box" style="align-items: center">
+          <span class="option-number" v-if="!item.picture_info"
+            >{{ computeOptionMethods[data.option_number_show_mode](index) }}
+          </span>
+          <!-- 语音作答 -->
+          <div v-if="isEnable(data.property.is_enable_voice_answer) && answer.answer_list[index]" class="sound-box">
+            <SoundRecordPreview
+              :disabled="disabled"
+              :wav-blob.sync="answer.answer_list[index].audio_file_id"
+              type="small"
+            />
+          </div>
         </div>
 
         <div v-if="isEnable(data.property.is_enable_reference_answer) && isShowRightAnswer" class="reference-box">
@@ -41,6 +49,7 @@
 import PreviewMixin from './components/PreviewMixin';
 import { GetFileStoreInfo } from '@/api/app';
 import SoundRecordPreview from './components/common/SoundRecordPreview.vue';
+import { computeOptionMethods } from '@/views/exercise_questions/data/common';
 
 export default {
   name: 'TalkPictruePreview',
@@ -50,6 +59,7 @@ export default {
   mixins: [PreviewMixin],
   data() {
     return {
+      computeOptionMethods,
       pic_list: {},
       active_index: 0,
     };
@@ -162,9 +172,19 @@ export default {
   .sound-box {
     width: max-content;
     padding: 4px;
-    margin-top: 8px;
     background: $content-color;
     border-radius: 40px;
   }
+
+  .content-box {
+    display: flex;
+    margin-top: 8px;
+
+    .option-number {
+      margin-right: 8px;
+      font-size: 16px;
+      line-height: 20px;
+    }
+  }
 }
 </style>

+ 17 - 10
src/views/exercise_questions/preview/WordCardPreview.vue

@@ -20,12 +20,12 @@
           pic_list[option_list[active_index].picture_file_id]
         "
         :src="pic_list[option_list[active_index].picture_file_id]"
-        fit="contain"
+        fit="cover"
       />
       <div class="words-right">
         <template v-for="(item, index) in option_list">
           <div v-if="index === active_index" :key="index" class="strock-box">
-            <div class="pinyin-box">
+            <div class="pinyin-box" v-if="item.audio_file_id || item.pinyin">
               <AudioPlay v-if="item.audio_file_id" :file-id="item.audio_file_id" theme-color="white" />
               <span class="pinyin">{{ item.pinyin }}</span>
             </div>
@@ -170,11 +170,11 @@ export default {
 
       label {
         display: block;
-        padding: 4px 16px;
+        padding: 8px 16px;
         margin-bottom: 4px;
-        font-size: 16px;
+        font-size: 18px;
         font-weight: 400;
-        line-height: 24px;
+        line-height: 26px;
         color: rgba(0, 0, 0, 30%);
         cursor: pointer;
         border-radius: 20px;
@@ -194,7 +194,14 @@ export default {
     }
 
     .strock-chinese {
+      width: 96px;
+      height: 96px;
       border: 1px solid #e81b1b;
+
+      :deep .strock-play-box {
+        width: 16px;
+        height: 16px;
+      }
     }
 
     .border-right-none {
@@ -233,22 +240,22 @@ export default {
   .tips {
     display: block;
     margin-bottom: 8px;
-    font-size: 14px;
+    font-size: 16px;
     font-weight: 400;
-    line-height: 22px;
+    line-height: 24px;
     color: rgba(0, 0, 0, 40%);
   }
 
   .example-sentence {
     margin: 0;
-    font-size: 14px;
-    line-height: 22px;
+    font-size: 16px;
+    line-height: 24px;
     color: #000;
   }
 
   .sound-box {
     width: max-content;
-    padding: 4px;
+    padding: 8px;
     background: $content-color;
     border-radius: 40px;
   }

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

@@ -17,7 +17,7 @@
           v-if="pic_list[item.picture_file_id]"
           style="width: 370px; height: 238px"
           :src="pic_list[item.picture_file_id]"
-          fit="contain"
+          fit="cover"
           :class="[active_index !== index ? 'not-active' : '']"
           @click="active_index = index"
         />