Jelajahi Sumber

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

dusenyao 1 tahun lalu
induk
melakukan
109cd9ee3b

TEMPAT SAMPAH
src/assets/select-tone-tips.png


TEMPAT SAMPAH
src/assets/tips-icon.png


+ 3 - 3
src/views/exercise_questions/create/components/create.vue

@@ -50,7 +50,7 @@ import ReadAloudQuestion from './exercises/ReadAloudQuestion.vue';
 import ChineseQuestion from './exercises/ChineseQuestion.vue';
 import WriteQuestion from './exercises/WriteQuestion.vue';
 import DialogueQuestion from './exercises/DialogueQuestion.vue';
-import TalkPicture from './exercises/TalkPicture.vue';
+import TalkPictureQuestion from './exercises/TalkPictureQuestion.vue';
 import ChooseToneQuestion from './exercises/ChooseToneQuestion.vue';
 import RepeatQuestion from './exercises/RepeatQuestion.vue';
 import ReadQuestion from './exercises/ReadQuestion.vue';
@@ -68,7 +68,7 @@ export default {
     ChineseQuestion,
     WriteQuestion,
     DialogueQuestion,
-    TalkPicture,
+    TalkPictureQuestion,
     ChooseToneQuestion,
     RepeatQuestion,
     ReadQuestion,
@@ -104,7 +104,7 @@ export default {
         chinese: ChineseQuestion,
         write: WriteQuestion,
         dialogue: DialogueQuestion,
-        talk_picture: TalkPicture,
+        talk_picture: TalkPictureQuestion,
         choose_tone: ChooseToneQuestion,
         repeat: RepeatQuestion,
         read: ReadQuestion,

+ 18 - 2
src/views/exercise_questions/create/components/exercises/ChineseQuestion.vue

@@ -40,10 +40,11 @@
             />
             <div v-else-if="data.other.audio_generation_method === 'auto'" class="auto-matically">
               <AudioPlay :file-id="item.audio_file_id" theme-color="gray" />
-              <span class="auto-btn" @click="handleMatically">自动生成</span>
+              <span class="auto-btn" @click="handleMatically(item)">自动生成</span>
             </div>
             <SoundRecord v-else :wav-blob.sync="item.audio_file_id" />
             <el-input v-if="data.property.learn_type !== 'learn'" v-model="item.definition" placeholder="输入释义" />
+            <el-input v-if="data.property.learn_type !== 'learn'" v-model="item.collocation" placeholder="输入搭配" />
             <SvgIcon icon-class="delete" class="delete pointer" @click="deleteOption(i, item.audio_file_id)" />
           </li>
         </ul>
@@ -132,6 +133,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 } from '@/views/exercise_questions/data/common';
 import {
@@ -174,7 +176,21 @@ export default {
       this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
     },
     // 自动生成音频
-    handleMatically() {},
+    handleMatically(item) {
+      if (item.pinyin.trim()) {
+        let MethodName = 'tool-PinyinToVoiceFile';
+        let data = {
+          pinyin: item.pinyin.trim(),
+        };
+        GetStaticResources(MethodName, data).then((res) => {
+          if (res.status === 1) {
+            this.data.file_id_list.splice(this.data.file_id_list.indexOf(item.file_id), 1);
+            item.audio_file_id = res.file_id;
+            this.data.file_id_list.push(res.file_id);
+          }
+        });
+      }
+    },
   },
 };
 </script>

+ 129 - 2
src/views/exercise_questions/create/components/exercises/ChooseToneQuestion.vue

@@ -42,7 +42,7 @@
             />
             <div v-else-if="data.other.audio_generation_method === 'auto'" class="auto-matically">
               <AudioPlay :file-id="item.audio_file_id" theme-color="gray" />
-              <span class="auto-btn" @click="handleMatically">自动生成</span>
+              <span class="auto-btn" @click="handleMatically(item)">自动生成</span>
             </div>
             <SoundRecord v-else :wav-blob.sync="item.audio_file_id" />
             <SvgIcon icon-class="delete" class="delete pointer" @click="deleteOption(i, item.audio_file_id)" />
@@ -144,6 +144,7 @@ import {
   toneList,
   toneTypeList,
 } from '@/views/exercise_questions/data/chooseTone';
+import { GetStaticResources } from '@/api/app';
 
 export default {
   name: 'ChooseToneQuestion',
@@ -159,6 +160,21 @@ export default {
       audioGenerationMethodList,
       toneTypeList,
       data: JSON.parse(JSON.stringify(ChooseToneData)),
+      matically_pinyin_obj: {}, // 存放转成声调的拼音
+      tone_data: [
+        ['ā', 'á', 'ǎ', 'à', 'a'],
+        ['ō', 'ó', 'ǒ', 'ò', 'o'],
+        ['ē', 'é', 'ě', 'è', 'e'],
+        ['ī', 'í', 'ǐ', 'ì', 'i'],
+        ['ū', 'ú', 'ǔ', 'ù', 'u'],
+        ['ǖ', 'ǘ', 'ǚ', 'ǜ', 'ü'],
+        ['Ā', 'Á', 'Â', 'À', 'A'],
+        ['Ō', 'Ó', 'Ô', 'Ò', 'O'],
+        ['Ē', 'É', 'Ê', 'È', 'E'],
+        ['Ī', 'Í', 'Î', 'Ì', 'I'],
+        ['Ū', 'Ú', 'Û', 'Ù', 'U'],
+      ],
+      res_arr: [],
     };
   },
   methods: {
@@ -179,7 +195,114 @@ export default {
       this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
     },
     // 自动生成音频
-    handleMatically() {},
+    handleMatically(item) {
+      if (item.content.trim()) {
+        let MethodName = 'tool-PinyinToVoiceFile';
+        let data = {
+          pinyin: this.matically_pinyin_obj[item.mark],
+        };
+        GetStaticResources(MethodName, data).then((res) => {
+          if (res.status === 1) {
+            this.data.file_id_list.splice(this.data.file_id_list.indexOf(item.file_id), 1);
+            item.audio_file_id = res.file_id;
+            this.data.file_id_list.push(res.file_id);
+          }
+        });
+      }
+    },
+    handleReplaceTone(e, mark) {
+      this.$nextTick(() => {
+        let value = e;
+        if (value) {
+          let reg = /\s+/g;
+          let valueArr = value.split(reg);
+          valueArr.forEach((item) => {
+            this.handleValue(item);
+          });
+          let str = '';
+          setTimeout(() => {
+            this.res_arr.forEach((item) => {
+              str += ' ';
+              item.forEach((sItem) => {
+                if (sItem.number && sItem.con) {
+                  let number = Number(sItem.number);
+                  let con = sItem.con;
+                  let word = this.addTone(number, con);
+                  str += word;
+                } else if (sItem.number) {
+                  str += sItem.number;
+                } else if (sItem.con) {
+                  str += ` ${sItem.con} `;
+                }
+              });
+            });
+            this.matically_pinyin_obj[mark] = str.trim().split(' ').join(',');
+          }, 10);
+        }
+      });
+    },
+    handleValue(valItem) {
+      let reg = /\d/;
+      let reg2 = /[A-Za-z]+\d/g;
+      let numList = [];
+      let valArr = valItem.split('');
+      if (reg2.test(valItem)) {
+        for (let i = 0; i < valArr.length; i++) {
+          let item = valItem[i];
+          if (reg.test(item)) {
+            let numIndex = numList.length === 0 ? 0 : numList[numList.length - 1].index;
+            let con = valItem.substring(numIndex, i);
+            con = con.replace(/\d/g, '');
+            let obj = {
+              index: i,
+              number: item,
+              con,
+              isTran: true,
+            };
+            numList.push(obj);
+          }
+        }
+      } else {
+        numList = [];
+      }
+      if (numList.length === 0) {
+        this.res_arr.push([{ con: valItem }]);
+      } else {
+        this.res_arr.push(numList);
+      }
+    },
+    addTone(number, con) {
+      let zmList = ['a', 'o', 'e', 'i', 'u', 'v', 'A', 'O', 'E', 'I', 'U'];
+      let cons = con;
+      if (number) {
+        for (let i = 0; i < zmList.length; i++) {
+          let zm = zmList[i];
+          if (con.indexOf(zm) > -1) {
+            let zm2 = this.tone_data[i][number - 1];
+            if (con.indexOf('iu') > -1) {
+              zm2 = this.tone_data[4][number - 1];
+              cons = con.replace('u', zm2);
+            } else if (con.indexOf('ui') > -1) {
+              zm2 = this.tone_data[3][number - 1];
+              cons = con.replace('i', zm2);
+            } else if (
+              con.indexOf('yv') > -1 ||
+              con.indexOf('jv') > -1 ||
+              con.indexOf('qv') > -1 ||
+              con.indexOf('xv') > -1
+            ) {
+              zm2 = this.tone_data[4][number - 1];
+              cons = con.replace('v', zm2);
+            } else {
+              cons = con.replace(zm, zm2);
+            }
+
+            break;
+          }
+        }
+      }
+      return cons;
+    },
     // 答案
     handleItemAnswer(item) {
       const index = this.data.answer.answer_list.findIndex((items) => items.mark === item.mark);
@@ -187,6 +310,8 @@ export default {
       let content_arr = content.split(' ');
       let select_item = '';
       let content_preview = '';
+      this.res_arr = [];
+      this.$set(this.matically_pinyin_obj, item.mark, []);
       content_arr.forEach((items) => {
         let items_trim = items.trim();
         if (items_trim) {
@@ -201,6 +326,7 @@ export default {
             select_item += `${items_trim} `;
           }
           content_preview += `${items_yuan} `;
+          this.handleReplaceTone(items_yuan + items_trim.substring(indexs, indexs + 1), item.mark);
         }
       });
       if (index === -1) {
@@ -213,6 +339,7 @@ export default {
         this.data.answer.answer_list[index].value = select_item.trim().split(' ');
       }
       item.content_view = content_preview.trim().split(' ');
+      // item.matically_pinyin = matically_pinyin.trim().split(' ').join(',');
     },
     // 改变类型
     handleChangeType() {

+ 0 - 164
src/views/exercise_questions/create/components/exercises/TalkPicture.vue

@@ -1,164 +0,0 @@
-<template>
-  <QuestionBase>
-    <template #content>
-      <div class="stem">
-        <el-input
-          v-if="data.property.stem_type === stemTypeList[0].value"
-          v-model="data.stem"
-          rows="3"
-          resize="none"
-          type="textarea"
-          placeholder="输入题干"
-        />
-
-        <RichText v-if="data.property.stem_type === stemTypeList[1].value" v-model="data.stem" placeholder="输入题干" />
-
-        <el-input
-          v-show="isEnable(data.property.is_enable_description)"
-          v-model="data.description"
-          rows="3"
-          resize="none"
-          type="textarea"
-          placeholder="输入描述"
-        />
-      </div>
-
-      <div class="content">
-        <el-upload ref="upload" action="no" accept="image/*" drag :show-file-list="false">
-          <div>点击或拖拽图片到此上传</div>
-          <div>只有 jpg, png, gif 等格式文件可以上传,文件大小不得超过 5MB</div>
-        </el-upload>
-      </div>
-
-      <div v-if="data.property.is_enable_reference_answer" class="reference-answer">
-        <span class="subtitle">参考答案:</span>
-        <el-input v-model="data.reference_answer" type="textarea" rows="3" placeholder="输入参考答案" />
-      </div>
-    </template>
-
-    <template #property>
-      <el-form :model="data.property">
-        <el-form-item label="题干">
-          <el-radio
-            v-for="{ value, label } in stemTypeList"
-            :key="value"
-            v-model="data.property.stem_type"
-            :label="value"
-          >
-            {{ label }}
-          </el-radio>
-        </el-form-item>
-        <el-form-item label="题号">
-          <el-input v-model="data.property.question_number" />
-        </el-form-item>
-        <el-form-item label-width="45px">
-          <el-radio
-            v-for="{ value, label } in questionNumberTypeList"
-            :key="value"
-            v-model="data.other.question_number_type"
-            :label="value"
-          >
-            {{ label }}
-          </el-radio>
-        </el-form-item>
-        <el-form-item label="描述">
-          <el-radio
-            v-for="{ value, label } in switchOption"
-            :key="value"
-            v-model="data.property.is_enable_description"
-            :label="value"
-          >
-            {{ 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 label-width="45px">
-          <el-input v-model="data.property.score" type="number" />
-        </el-form-item>
-        <el-form-item label="语音作答">
-          <el-radio
-            v-for="{ value, label } in switchOption"
-            :key="value"
-            v-model="data.property.is_enable_voice_answer"
-            :label="value"
-          >
-            {{ label }}
-          </el-radio>
-        </el-form-item>
-        <el-form-item label="参考答案">
-          <el-radio
-            v-for="{ value, label } in switchOption"
-            :key="value"
-            v-model="data.property.is_enable_reference_answer"
-            :label="value"
-          >
-            {{ label }}
-          </el-radio>
-        </el-form-item>
-      </el-form>
-    </template>
-  </QuestionBase>
-</template>
-
-<script>
-import QuestionMixin from '../common/QuestionMixin.js';
-
-import { talkPictrueData } from '@/views/exercise_questions/data/talkPicture';
-
-export default {
-  name: 'TalkPicture',
-  mixins: [QuestionMixin],
-  data() {
-    return {
-      data: JSON.parse(JSON.stringify(talkPictrueData)),
-    };
-  },
-  methods: {},
-};
-</script>
-
-<style lang="scss" scoped>
-.content {
-  :deep .el-upload {
-    width: 100%;
-
-    &-dragger {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-      width: 100%;
-      height: 90px;
-      font-size: 14px;
-
-      :first-child {
-        color: #000;
-      }
-
-      :last-child {
-        color: $text-color;
-      }
-    }
-  }
-}
-
-.reference-answer {
-  display: flex;
-  flex-direction: column;
-  row-gap: 8px;
-  margin-top: 8px;
-
-  .subtitle {
-    font-size: 14px;
-  }
-}
-</style>

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

@@ -0,0 +1,299 @@
+<template>
+  <QuestionBase>
+    <template #content>
+      <div class="stem">
+        <el-input
+          v-if="data.property.stem_type === stemTypeList[0].value"
+          v-model="data.stem"
+          rows="3"
+          resize="none"
+          type="textarea"
+          placeholder="输入题干"
+        />
+
+        <RichText v-if="data.property.stem_type === stemTypeList[1].value" v-model="data.stem" placeholder="输入题干" />
+
+        <el-input
+          v-show="isEnable(data.property.is_enable_description)"
+          v-model="data.description"
+          rows="3"
+          resize="none"
+          type="textarea"
+          placeholder="输入描述"
+        />
+      </div>
+
+      <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]">
+            <div class="item-left">
+              <el-image
+                style="width: 72px; height: 72px"
+                :src="pic_list[item.picture_file_id]"
+                :preview-src-list="[pic_list[item.picture_file_id]]"
+                fit="contain"
+              />
+              <button class="delete-btn" @click="delectOptions(index, item.picture_file_id)">
+                <i class="el-icon-delete"></i>删除
+              </button>
+            </div>
+            <div class="item-right">
+              <div class="item-rich">
+                <label class="">图片标题</label>
+                <RichText v-model="item.picture_title" placeholder="输入图片标题" />
+              </div>
+              <div class="item-rich">
+                <label class="">图片信息</label>
+                <RichText v-model="item.picture_info" placeholder="输入图片信息" />
+              </div>
+              <div v-if="isEnable(data.property.is_enable_reference_answer)" class="item-rich">
+                <label class="">参考答案</label>
+                <RichText v-model="item.reference_answer" placeholder="输入参考答案" />
+              </div>
+            </div>
+          </template>
+        </div>
+        <el-upload
+          ref="upload"
+          action="no"
+          accept=".jpg,.png,.gif"
+          drag
+          :show-file-list="false"
+          :before-upload="beforeUpload"
+          :http-request="upload"
+          :on-exceed="handleExceed"
+        >
+          <div>点击或拖拽图片到此上传</div>
+          <div>只有 jpg, png, gif 等格式文件可以上传,文件大小不得超过 5MB</div>
+        </el-upload>
+      </div>
+    </template>
+
+    <template #property>
+      <el-form :model="data.property">
+        <el-form-item label="题干">
+          <el-radio
+            v-for="{ value, label } in stemTypeList"
+            :key="value"
+            v-model="data.property.stem_type"
+            :label="value"
+          >
+            {{ label }}
+          </el-radio>
+        </el-form-item>
+        <el-form-item label="题号">
+          <el-input v-model="data.property.question_number" />
+        </el-form-item>
+        <el-form-item label-width="45px">
+          <el-radio
+            v-for="{ value, label } in questionNumberTypeList"
+            :key="value"
+            v-model="data.other.question_number_type"
+            :label="value"
+          >
+            {{ label }}
+          </el-radio>
+        </el-form-item>
+        <el-form-item label="描述">
+          <el-radio
+            v-for="{ value, label } in switchOption"
+            :key="value"
+            v-model="data.property.is_enable_description"
+            :label="value"
+          >
+            {{ 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 label-width="45px">
+          <el-input v-model="data.property.score" type="number" />
+        </el-form-item>
+        <el-form-item label="语音作答">
+          <el-radio
+            v-for="{ value, label } in switchOption"
+            :key="value"
+            v-model="data.property.is_enable_voice_answer"
+            :label="value"
+          >
+            {{ label }}
+          </el-radio>
+        </el-form-item>
+        <el-form-item label="参考答案">
+          <el-radio
+            v-for="{ value, label } in switchOption"
+            :key="value"
+            v-model="data.property.is_enable_reference_answer"
+            :label="value"
+          >
+            {{ label }}
+          </el-radio>
+        </el-form-item>
+      </el-form>
+    </template>
+  </QuestionBase>
+</template>
+
+<script>
+import QuestionMixin from '../common/QuestionMixin.js';
+
+import { talkPictrueData, getOption } from '@/views/exercise_questions/data/talkPicture';
+import { fileUpload, GetFileStoreInfo } from '@/api/app';
+
+export default {
+  name: 'TalkPicture',
+  mixins: [QuestionMixin],
+  data() {
+    return {
+      data: JSON.parse(JSON.stringify(talkPictrueData)),
+      pic_list: {},
+      is_first: true,
+    };
+  },
+  watch: {
+    'data.file_id_list': {
+      handler() {
+        if (this.is_first) {
+          this.handleData();
+        }
+      },
+      deep: true,
+    },
+  },
+  created() {},
+  mounted() {},
+  methods: {
+    // 初始化数据
+    handleData() {
+      this.data.file_id_list.forEach((item) => {
+        GetFileStoreInfo({ file_id: item }).then(({ file_id, file_url }) => {
+          this.$set(this.pic_list, file_id, file_url);
+        });
+      });
+      this.is_first = false;
+    },
+    // 删除
+    delectOptions(i, id) {
+      this.$confirm('是否删除该条全部信息?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(() => {
+          delete this.pic_list[id];
+          this.data.file_id_list.splice(this.data.file_id_list.indexOf(id), 1);
+          this.data.option_list.splice(i, 1);
+        })
+        .catch(() => {});
+    },
+    beforeUpload(file) {
+      // 可以用来限制文件大小
+      if (file.size > 5 * 1024 * 1024) {
+        this.$message.warning('上传图片大小不能超过5M');
+        return false; // 必须返回false
+      }
+    },
+    upload(file) {
+      fileUpload('Mid', file).then(({ file_info_list }) => {
+        if (file_info_list.length > 0) {
+          const { file_id, file_url } = file_info_list[0];
+          this.data.file_id_list.push(file_id);
+          this.data.option_list.push(getOption());
+          this.data.option_list[this.data.option_list.length - 1].picture_file_id = file_id;
+          this.$set(this.pic_list, file_id, file_url);
+        }
+      });
+    },
+    handleExceed(files, fileList) {
+      this.$message.warning(
+        `当前限制选择 ${this.filleNumber ? this.filleNumber : 1} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${
+          files.length + fileList.length
+        } 个文件`,
+      );
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.content {
+  :deep .el-upload {
+    width: 100%;
+
+    &-dragger {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      height: 90px;
+      font-size: 14px;
+
+      :first-child {
+        color: #000;
+      }
+
+      :last-child {
+        color: $text-color;
+      }
+    }
+  }
+
+  .content-item {
+    display: flex;
+    column-gap: 8px;
+    margin-bottom: 24px;
+  }
+
+  .delete-btn {
+    width: 100%;
+    padding: 5px 8px;
+    font-size: 14px;
+    line-height: 22px;
+    color: #f53f3f;
+    background: #fff4f4;
+    border: none;
+    border-radius: 4px;
+
+    .el-icon-delete {
+      margin-right: 8px;
+    }
+
+    &:hover {
+      color: #f53f3f;
+    }
+
+    &:focus {
+      outline: none;
+    }
+  }
+
+  .item-left {
+    width: 72px;
+  }
+
+  .item-right {
+    flex: 1;
+
+    .item-rich {
+      display: flex;
+
+      > label {
+        flex-shrink: 0;
+        width: 64px;
+        font-size: 14px;
+        line-height: 32px;
+        color: #4e5969;
+      }
+    }
+  }
+}
+</style>

+ 4 - 4
src/views/exercise_questions/create/components/exercises/WriteQuestion.vue

@@ -19,13 +19,13 @@
           rows="3"
           resize="none"
           type="textarea"
-          placeholder="输入描述"
+          placeholder="输入题目要求"
         />
       </div>
 
       <div class="content">
-        <label class="title-little">文章:</label>
-        <RichText v-model="data.article" placeholder="输入文章" />
+        <label class="title-little">阅读材料:</label>
+        <RichText v-model="data.article" placeholder="输入阅读材料" />
         <template v-if="isEnable(data.property.is_enable_sample_text)">
           <el-divider class="write-divider" />
           <label class="title-little">范文:</label>
@@ -60,7 +60,7 @@
             {{ label }}
           </el-radio>
         </el-form-item>
-        <el-form-item label="描述">
+        <el-form-item label="题目要求">
           <el-radio
             v-for="{ value, label } in switchOption"
             :key="value"

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

@@ -8,6 +8,7 @@ export function getOption(content = '') {
     audio_file_id: '',
     pinyin: '',
     definition: '',
+    collocation: '',
   };
 }
 // 汉字类型列表

+ 10 - 7
src/views/exercise_questions/data/talkPicture.js

@@ -1,21 +1,24 @@
-import { stemTypeList, scoreTypeList, questionNumberTypeList } from './common';
-
+import { stemTypeList, scoreTypeList, questionNumberTypeList, switchOption } from './common';
+import { getRandomNumber } from '@/utils/index';
+export function getOption() {
+  return { picture_title: '', picture_info: '', reference_answer: '', picture_file_id: '', mark: getRandomNumber() };
+}
 // 看图说话数据模板
 export const talkPictrueData = {
   type: 'talk_picture', // 题型
   stem: '', // 题干
-  reference_answer: '', // 参考答案
   description: '', // 描述
-  option_list: '', // 选项
+  option_list: [], // 选项
+  file_id_list: [],
   answer: { score: 0, score_type: scoreTypeList[0].value }, // 答案
   // 题型属性
   property: {
     stem_type: stemTypeList[0].value, // 题干类型
     question_number: '1', // 题号
     score: 1, // 分值
-    is_enable_description: false, // 描述
-    is_enable_voice_answer: true, // 语音作答
-    is_enable_reference_answer: true, // 参考答案
+    is_enable_description: switchOption[1].value, // 描述
+    is_enable_voice_answer: switchOption[0].value, // 语音作答
+    is_enable_reference_answer: switchOption[0].value, // 参考答案
     score_type: scoreTypeList[0].value, // 分值类型
   },
   // 其他属性

+ 14 - 11
src/views/exercise_questions/preview/ChinesePreview.vue

@@ -10,7 +10,7 @@
     <!-- 笔画学习 -->
     <div :class="['words-box', 'words-box-' + data.property.learn_type]">
       <div v-for="(item, index) in data.option_list" :key="index" :class="['words-item']">
-        <template v-if="item.content.trim()">
+        <template v-if="item.content && item.content.trim() && item.strokes">
           <div
             v-if="data.property.learn_type !== 'learn'"
             class="words-top"
@@ -26,6 +26,7 @@
               <span class="pinyin">{{ item.pinyin }}</span>
             </div>
             <p class="words-right">{{ item.definition }}</p>
+            <p class="words-right">{{ item.collocation }}</p>
           </div>
           <template v-if="data.property.learn_type === 'paint'">
             <!-- 描红 -->
@@ -120,6 +121,7 @@ import PreviewMixin from './components/PreviewMixin';
 import Strockplayredline from './components/common/Strockplayredline.vue';
 import Strockred from './components/common/Strockred.vue';
 import FreewriteLettle from './components/common/FreewriteLettle.vue';
+import { GetStaticResources } from '@/api/app';
 
 export default {
   name: 'ChinesePreview',
@@ -166,7 +168,6 @@ export default {
       if (
         document.getElementsByClassName('preview-content') &&
         document.getElementsByClassName('preview-content')[0] &&
-        this.data.property.learn_type !== 'learn' &&
         !this.writer_number
       ) {
         this.writer_number_yuan =
@@ -180,18 +181,18 @@ export default {
       this.data.option_list.forEach((item) => {
         if (item.content.trim()) {
           let arr = [];
-          // let MethodName = 'hz_resource_manager-GetHZStrokesContent';
-          // let data = {
-          //   hz: item.content.trim(),
-          // };
-          // GetStaticResources(MethodName, data).then((res) => {
-          //   this.$set(item, 'strokes', res);
-          // });
+          let MethodName = 'hz_resource_manager-GetHZStrokesContent';
+          let data = {
+            hz: item.content.trim(),
+          };
+          GetStaticResources(MethodName, data).then((res) => {
+            this.$set(item, 'strokes', res);
+          });
           for (let i = 0; i < this.writer_number; i++) {
             arr.push(null);
           }
           item.imgArr = arr;
-          this.answer_list.write_model[item.content.trim()] = arr;
+          this.answer_list.write_model[item.content] = arr;
         }
       });
     },
@@ -251,6 +252,7 @@ export default {
     .words-top {
       display: flex;
       width: 100%;
+      min-height: 30px;
       border: 1px solid #e81b1b;
       border-bottom: none;
 
@@ -261,11 +263,12 @@ export default {
         align-items: center;
         justify-content: center;
         width: 64px;
+        margin-right: 12px;
         border-right: 1px solid #e81b1b;
       }
 
       .words-right {
-        padding: 8px 16px;
+        padding: 8px 4px;
         margin: 0;
         font-size: 14px;
         line-height: 14px;

+ 42 - 0
src/views/exercise_questions/preview/ChooseTonePreview.vue

@@ -60,6 +60,12 @@
         </span>
       </li>
     </div>
+    <div class="answer-tips-box">
+      <img v-if="show_tips" class="answer-tips" src="../../../assets/select-tone-tips.png" />
+      <span :class="['tips-btn', show_tips ? 'tips-btn-active' : '']" @click="show_tips = !show_tips">
+        <img src="../../../assets/tips-icon.png" />
+      </span>
+    </div>
   </div>
 </template>
 
@@ -99,6 +105,7 @@ export default {
       active_letter: '', // 选中字母的值
       active_letter_index: 0, // 选择字母索引
       select_item_index: 0, // 小题索引
+      show_tips: false, // 是否显示答题提示
     };
   },
   created() {
@@ -264,6 +271,9 @@ export default {
 .choosetone-preview {
   @include preview;
 
+  position: relative;
+  min-height: 450px;
+
   .option-list {
     display: flex;
     flex-wrap: wrap;
@@ -315,5 +325,37 @@ export default {
       }
     }
   }
+
+  .answer-tips-box {
+    position: absolute;
+    top: 24px;
+    right: 24px;
+    display: flex;
+
+    .answer-tips {
+      width: 307px;
+      margin-top: 32px;
+    }
+
+    .tips-btn {
+      width: 34px;
+      height: 34px;
+      padding: 5px;
+      font-size: 0;
+      background: #fff5e3;
+      border: 1px solid rgba(0, 0, 0, 8%);
+      border-radius: 8px;
+
+      &-active {
+        background: #e3d5b3;
+      }
+
+      img {
+        width: 22px;
+        height: 22px;
+        cursor: pointer;
+      }
+    }
+  }
 }
 </style>

+ 7 - 7
src/views/exercise_questions/preview/RepeatPreview.vue

@@ -62,21 +62,21 @@ export default {
   @include preview;
 
   .option-list {
-    display: flex;
-    flex-wrap: wrap;
-    row-gap: 16px;
+    // display: flex;
+    // flex-wrap: wrap;
+    // row-gap: 16px;
 
     .option-item {
       display: flex;
       column-gap: 16px;
       align-items: center;
-      min-width: 40%;
-      max-width: 50%;
-      padding-right: 24px;
+      width: 90%;
+      margin-bottom: 16px;
 
       .option-content {
         flex: 1;
-        max-width: 306px;
+
+        // max-width: 306px;
         padding: 8px 16px;
         color: #706f78;
         background-color: $content-color;

+ 36 - 11
src/views/exercise_questions/preview/SortPreview.vue

@@ -21,7 +21,12 @@
         <div
           v-for="(itemNode, indexNode) in move_list"
           :key="indexNode"
-          :class="['drag-item', itemNode.correct == 'correct' ? 'correct' : 'error']"
+          :class="[
+            'drag-item',
+            itemNode.correct == 'correct' ? 'correct' : 'error',
+            click_index_list.indexOf(indexNode) > -1 ? 'drag-item-active' : '',
+          ]"
+          @click="handleClickItem(indexNode)"
         >
           <SvgIcon
             v-if="data.property.layout_type === 'vertical'"
@@ -56,6 +61,7 @@ export default {
       answer_list: [], // 存储用户答题
       move_list: [], // 移动后的数组
       drag: false,
+      click_index_list: [], // 点击选中的索引
     };
   },
   watch: {
@@ -68,6 +74,13 @@ export default {
       },
       deep: true,
     },
+    click_index_list: {
+      handler(val) {
+        if (val.length === 2) {
+          this.changeSort();
+        }
+      },
+    },
   },
   created() {
     this.handleData();
@@ -78,11 +91,7 @@ export default {
     handleData() {
       let sort_list = this.shuffle(JSON.parse(JSON.stringify(this.data.option_list)));
       sort_list.forEach((item, index) => {
-        if (item.mark === this.data.option_list[index].mark) {
-          item.correct = true;
-        } else {
-          item.correct = false;
-        }
+        item.correct = item.mark === this.data.option_list[index].mark;
       });
       this.move_list = sort_list;
     },
@@ -106,13 +115,22 @@ export default {
     // 判断对错
     changeuserAnswerJudge() {
       this.move_list.forEach((item, index) => {
-        if (item.mark === this.data.option_list[index].mark) {
-          item.correct = true;
-        } else {
-          item.correct = false;
-        }
+        item.correct = item.mark === this.data.option_list[index].mark;
       });
     },
+    // 点击item
+    handleClickItem(indexNode) {
+      // 查看答案模式下需要禁用
+      this.click_index_list.push(indexNode);
+    },
+    // 点击交换
+    changeSort() {
+      [this.move_list[this.click_index_list[0]], this.move_list[this.click_index_list[1]]] = [
+        this.move_list[this.click_index_list[1]],
+        this.move_list[this.click_index_list[0]],
+      ];
+      this.click_index_list = [];
+    },
   },
 };
 </script>
@@ -143,6 +161,7 @@ export default {
       max-width: 800px;
       padding: 8px 16px;
       background: $content-color;
+      border: 1px solid #f9f8f9;
       border-radius: 4px;
 
       :deep p {
@@ -173,6 +192,12 @@ export default {
         margin: 16px auto 0;
       }
     }
+
+    .drag-item-active {
+      .drag-content {
+        border-color: #306eff;
+      }
+    }
   }
 }
 </style>

+ 147 - 2
src/views/exercise_questions/preview/TalkPictruePreview.vue

@@ -6,19 +6,94 @@
       <span v-html="sanitizeHTML(data.stem)"></span>
     </div>
     <div v-if="isEnable(data.property.is_enable_description)" class="description">{{ data.description }}</div>
+    <div class="content">
+      <div class="content-left">
+        <el-carousel
+          type="card"
+          height="276px"
+          :autoplay="false"
+          indicator-position="none"
+          arrow="never"
+          @change="changeImg"
+        >
+          <el-carousel-item v-for="(item, index) in data.option_list" :key="index">
+            <el-image
+              v-if="pic_list[item.picture_file_id]"
+              style="width: 370px; height: 276px"
+              :src="pic_list[item.picture_file_id]"
+              fit="contain"
+            />
+          </el-carousel-item>
+        </el-carousel>
+        <h3 class="pic-title" v-html="sanitizeHTML(data.option_list[active_index].picture_title)"></h3>
+        <p class="pic-info" v-html="sanitizeHTML(data.option_list[active_index].picture_info)"></p>
+      </div>
+      <div class="content-right">
+        <el-input
+          v-model="answer_list[active_index].value"
+          rows="12"
+          resize="none"
+          type="textarea"
+          placeholder="请输入"
+        />
+        <template v-if="isEnable(data.property.is_enable_voice_answer)">
+          <!-- 语音作答 -->
+          <SoundRecordPreview :wav-blob.sync="answer_list[active_index].audio_file_id" />
+        </template>
+      </div>
+    </div>
+    <div v-if="isEnable(data.property.is_enable_reference_answer) && 1 === 2" class="reference-box">
+      <h5 class="reference-title">参考答案</h5>
+      <span class="reference-answer" v-html="sanitizeHTML(data.option_list[active_index].reference_answer)"></span>
+    </div>
   </div>
 </template>
 
 <script>
 import PreviewMixin from './components/PreviewMixin';
+import { GetFileStoreInfo } from '@/api/app';
+import SoundRecordPreview from './components/common/SoundRecordPreview.vue';
 
 export default {
   name: 'TalkPictruePreview',
+  components: {
+    SoundRecordPreview,
+  },
   mixins: [PreviewMixin],
   data() {
-    return {};
+    return {
+      pic_list: {},
+      active_index: 0,
+      answer_list: [],
+    };
+  },
+  created() {
+    this.handleData();
+  },
+  methods: {
+    // 初始化数据
+    handleData() {
+      this.answer_list = [];
+      this.pic_list = {};
+      this.active_index = 0;
+      this.data.file_id_list.forEach((item) => {
+        GetFileStoreInfo({ file_id: item }).then(({ file_id, file_url }) => {
+          this.$set(this.pic_list, file_id, file_url);
+        });
+      });
+      this.data.option_list.forEach((item) => {
+        let obj = {
+          mark: item.mark,
+          value: '',
+          audio_file_id: '',
+        };
+        this.answer_list.push(obj);
+      });
+    },
+    changeImg(index) {
+      this.active_index = index;
+    },
   },
-  methods: {},
 };
 </script>
 
@@ -27,5 +102,75 @@ export default {
 
 .talkpictrue-preview {
   @include preview;
+
+  :deep p {
+    margin: 0;
+  }
+
+  .content {
+    display: flex;
+    column-gap: 24px;
+
+    &-left {
+      flex-shrink: 0;
+      width: 478px;
+
+      :deep .el-carousel__item--card {
+        width: 77%;
+        margin-left: -13.5%;
+      }
+
+      .el-image {
+        opacity: 0.2;
+      }
+
+      .el-carousel__item--card.is-active {
+        .el-image {
+          background: #fff;
+          opacity: 1;
+        }
+      }
+
+      .pic-title {
+        margin: 8px 0 4px;
+        font-size: 12px;
+        font-weight: 600;
+        line-height: 20px;
+        color: #000;
+        word-break: break-word;
+      }
+
+      .pic-info {
+        margin: 0;
+        font-size: 12px;
+        font-weight: 400;
+        line-height: 20px;
+        color: #000;
+        word-break: break-word;
+      }
+    }
+
+    &-right {
+      flex: 1;
+
+      .el-textarea {
+        height: 276px;
+        margin-bottom: 16px;
+      }
+    }
+  }
+
+  .reference-box {
+    padding: 12px;
+    background: #f9f8f9;
+
+    .reference-title {
+      margin: 0 0 10px;
+      font-size: 14px;
+      font-weight: 400;
+      line-height: 32px;
+      color: #4e5969;
+    }
+  }
 }
 </style>

+ 6 - 0
src/views/exercise_questions/preview/WritePreview.vue

@@ -14,6 +14,7 @@
       placeholder="请输入内容"
       :maxlength="data.property.word_num"
       show-word-limit
+      @input="handleInput"
     />
     <template v-if="isEnable(data.property.is_enable_voice_answer)">
       <!-- 语音作答 -->
@@ -76,6 +77,11 @@ export default {
     handleDelete(fileId) {
       this.user_answer.accessory_file_id.splice(this.user_answer.accessory_file_id.indexOf(fileId), 1);
     },
+    handleInput(value) {
+      if (value.length >= this.data.property.word_num) {
+        this.$message.warning(`字数达到${value.length}字!`);
+      }
+    },
   },
 };
 </script>

+ 6 - 1
src/views/exercise_questions/preview/components/common/SoundRecordPreview.vue

@@ -85,7 +85,12 @@ export default {
   watch: {
     wavBlob: {
       handler(val) {
-        if (!val) return;
+        if (!val) {
+          this.file_url = '';
+          this.recordTime = 0;
+          this.recordTimes = 0;
+          return;
+        }
         GetFileStoreInfo({ file_id: val }).then(({ file_url, media_duration }) => {
           this.file_url = file_url;
           this.recordTime = media_duration;