natasha 1 vuosi sitten
vanhempi
commit
aeb3a294fc

+ 5 - 1
src/views/exercise_questions/create/components/common/UploadDrag.vue

@@ -27,6 +27,10 @@ export default {
       type: Number,
       default: 1,
     },
+    itemIndex: {
+      type: Number,
+      default: null,
+    },
   },
   data() {
     return {};
@@ -44,7 +48,7 @@ export default {
       fileUpload('Mid', file, { isGlobalprogress: true }).then(({ file_info_list }) => {
         if (file_info_list.length > 0) {
           const { file_id, file_url } = file_info_list[0];
-          this.$emit('fileUploadSuccess', file_id, file_url);
+          this.$emit('fileUploadSuccess', file_id, file_url, this.itemIndex);
         }
       });
     },

+ 9 - 3
src/views/exercise_questions/create/components/exercises/ChineseQuestion.vue

@@ -54,7 +54,7 @@
               @deleteFile="deleteFiles"
             />
             <div v-else-if="data.other.audio_generation_method === 'auto'" class="auto-matically">
-              <AudioPlay :file-id="item.audio_file_id" theme-color="gray" />
+              <AudioPlay :file-id="item.audio_file_id" theme-color="gray" v-if="item.audio_file_id" />
               <span class="auto-btn" @click="handleMatically(item)">自动生成</span>
             </div>
             <SoundRecord v-else :wav-blob.sync="item.audio_file_id" />
@@ -208,8 +208,14 @@ export default {
     },
     // 删除小题
     deleteOption(i, file_id) {
-      this.data.option_list.splice(i, 1);
-      this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
+      this.$confirm('是否删除?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }).then(() => {
+        this.data.option_list.splice(i, 1);
+        this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
+      });
     },
     // 自动生成音频
     handleMatically(item) {

+ 13 - 3
src/views/exercise_questions/create/components/exercises/ChooseToneQuestion.vue

@@ -41,7 +41,7 @@
               @deleteFile="deleteFiles"
             />
             <div v-else-if="data.other.audio_generation_method === 'auto'" class="auto-matically">
-              <AudioPlay :file-id="item.audio_file_id" theme-color="gray" />
+              <AudioPlay :file-id="item.audio_file_id" theme-color="gray" v-if="item.audio_file_id" />
               <span class="auto-btn" @click="handleMatically(item)">自动生成</span>
             </div>
             <SoundRecord v-else :wav-blob.sync="item.audio_file_id" />
@@ -168,6 +168,7 @@ export default {
       res_arr: [],
     };
   },
+  created() {},
   methods: {
     addOption() {
       this.data.option_list.push(getOption());
@@ -182,12 +183,21 @@ export default {
     },
     // 删除小题
     deleteOption(i, file_id) {
-      this.data.option_list.splice(i, 1);
-      this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
+      this.$confirm('是否删除?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }).then(() => {
+        this.data.option_list.splice(i, 1);
+        this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
+      });
     },
     // 自动生成音频
     handleMatically(item) {
       if (item.content.trim()) {
+        if (!this.matically_pinyin_obj[item.mark]) {
+          this.handleItemAnswer(item);
+        }
         let MethodName = 'tool-PinyinToVoiceFile';
         let data = {
           pinyin: this.matically_pinyin_obj[item.mark],

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

@@ -160,8 +160,14 @@ export default {
     },
     // 删除小题
     deleteOption(i, file_id) {
-      this.data.option_list.splice(i, 1);
-      this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
+      this.$confirm('是否删除?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }).then(() => {
+        this.data.option_list.splice(i, 1);
+        this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
+      });
     },
   },
 };

+ 134 - 28
src/views/exercise_questions/create/components/exercises/WordCardQuestion.vue

@@ -27,29 +27,52 @@
         <label class="title-little">字词:</label>
         <ul>
           <li v-for="(item, i) in data.option_list" :key="i" class="content-word-card">
-            <UploadDrag @fileUploadSuccess="fileUploadSuccess" :limit="1" ref="uploadDrag"></UploadDrag>
-            <div class="word-card">
-              <el-input v-model="item.content" :placeholder="'输入汉字或词汇'" />
-              <el-input v-model="item.pinyin" :placeholder="'输入拼音'" />
-              <UploadAudio
-                v-if="data.other.audio_generation_method === 'upload'"
-                :key="item.audio_file_id || i"
-                :file-id="item.audio_file_id"
-                :item-index="i"
-                :show-upload="!item.audio_file_id"
-                @upload="uploads"
-                @deleteFile="deleteFiles"
+            <div class="item-left" v-if="item.picture_file_id">
+              <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"
               />
-              <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(item)">自动生成</span>
+              <el-button size="small" class="delete-btn" @click="delectOptions(i, item.picture_file_id)">
+                <i class="el-icon-delete"></i>删除
+              </el-button>
+            </div>
+            <UploadDrag
+              v-else
+              @fileUploadSuccess="fileUploadSuccess"
+              :itemIndex="i"
+              :limit="1"
+              ref="uploadDrag"
+            ></UploadDrag>
+            <div class="word-card-item">
+              <div class="word-card">
+                <el-input v-model="item.content" :placeholder="'输入汉字或词汇'" />
+                <el-input v-model="item.pinyin" :placeholder="'拼音间用空格隔开'" />
+                <UploadAudio
+                  v-if="data.other.audio_generation_method === 'upload'"
+                  :key="item.audio_file_id || i"
+                  :file-id="item.audio_file_id"
+                  :item-index="i"
+                  :show-upload="!item.audio_file_id"
+                  @upload="uploads"
+                  @deleteFile="deleteFiles"
+                />
+                <div v-else-if="data.other.audio_generation_method === 'auto'" class="auto-matically">
+                  <AudioPlay :file-id="item.audio_file_id" theme-color="gray" v-if="item.audio_file_id" />
+                  <span class="auto-btn" @click="handleMatically(item)">自动生成</span>
+                </div>
+                <SoundRecord v-else :wav-blob.sync="item.audio_file_id" />
+                <span class="rate-box"><el-rate v-model="item.rate"></el-rate></span>
+                <el-input v-model="item.definition" placeholder="输入释义" type="textarea" :rows="1" />
               </div>
-              <SoundRecord v-else :wav-blob.sync="item.audio_file_id" />
-              <span><el-rate v-model="item.rate"></el-rate></span>
-              <el-input v-model="item.definition" placeholder="输入释义" type="textarea" />
+              <SvgIcon
+                icon-class="delete"
+                class="delete pointer"
+                @click="deleteOption(i, item.audio_file_id, item.picture_file_id)"
+              />
             </div>
 
-            <SvgIcon icon-class="delete" class="delete pointer" @click="deleteOption(i, item.audio_file_id)" />
             <div class="example-sentence-box">
               <div class="example-sentence" v-for="(items, indexs) in item.example_sentence" :key="indexs">
                 <span class="question-number" @dblclick="changeOptionType(data)" title="双击切换序号类型">
@@ -140,6 +163,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 { GetFileStoreInfo } from '@/api/app';
 import { changeOptionType, handleInputNumber } from '@/views/exercise_questions/data/common';
 import UploadDrag from '../common/UploadDrag.vue';
 
@@ -166,9 +190,29 @@ export default {
       changeOptionType,
       handleInputNumber,
       pic_list: {},
+      is_first: true,
     };
   },
+  watch: {
+    'data.file_id_list': {
+      handler() {
+        if (this.is_first) {
+          this.handleData();
+        }
+      },
+      deep: true,
+    },
+  },
   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;
+    },
     addOption() {
       this.data.option_list.push(getOption());
     },
@@ -181,16 +225,38 @@ export default {
       this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
     },
     // 删除小题
-    deleteOption(i, file_id) {
-      this.data.option_list.splice(i, 1);
-      this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
+    deleteOption(i, file_id, pic_id) {
+      this.$confirm('是否删除?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }).then(() => {
+        this.data.option_list.splice(i, 1);
+        this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
+        this.data.file_id_list.splice(this.data.file_id_list.indexOf(pic_id), 1);
+      });
+    },
+    // 删除
+    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[i].picture_file_id = '';
+          this.$refs.uploadDrag.clearFiles();
+        })
+        .catch(() => {});
     },
     // 自动生成音频
     handleMatically(item) {
       if (item.pinyin.trim()) {
         let MethodName = 'tool-PinyinToVoiceFile';
         let data = {
-          pinyin: item.pinyin.trim(),
+          pinyin: item.pinyin.trim().split(' ').join(','),
         };
         GetStaticResources(MethodName, data).then((res) => {
           if (res.status === 1) {
@@ -201,10 +267,9 @@ export default {
         });
       }
     },
-    fileUploadSuccess(file_id, file_url) {
+    fileUploadSuccess(file_id, file_url, index) {
       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.data.option_list[index].picture_file_id = file_id;
       this.$set(this.pic_list, file_id, file_url);
     },
   },
@@ -239,13 +304,54 @@ export default {
   }
 
   .content-word-card {
+    .item-left {
+      display: flex;
+      column-gap: 8px;
+      align-items: flex-end;
+    }
+
+    .word-card-item {
+      display: flex;
+      column-gap: 8px;
+      margin: 16px 0;
+    }
+
     .word-card {
       display: flex;
+      flex: 1;
       flex-wrap: wrap;
-      column-gap: 4px;
+      gap: 8px 4px;
 
       .el-input {
-        width: 255px;
+        flex: 1;
+      }
+
+      .rate-box {
+        width: 128px;
+        height: 32px;
+        padding: 6px 0;
+        text-align: center;
+        background: #f2f3f5;
+        border-radius: 2px;
+      }
+    }
+
+    .example-sentence-box {
+      .example-sentence {
+        display: flex;
+        column-gap: 8px;
+        margin-bottom: 8px;
+
+        .question-number {
+          min-width: 40px;
+          height: 32px;
+          padding: 4px 0;
+          color: $text-color;
+          text-align: center;
+          cursor: pointer;
+          background-color: $fill-color;
+          border-radius: 2px;
+        }
       }
     }
 

+ 191 - 168
src/views/exercise_questions/preview/WordCardPreview.vue

@@ -1,6 +1,6 @@
 <!-- eslint-disable vue/no-v-html -->
 <template>
-  <div class="chinese-preview">
+  <div class="word-card-preview">
     <div class="stem">
       <span class="question-number">{{ data.property.question_number }}.</span>
       <span v-html="sanitizeHTML(data.stem)"></span>
@@ -8,8 +8,75 @@
     <div v-if="isEnable(data.property.is_enable_description)" class="description">{{ data.description }}</div>
 
     <!-- 笔画学习 -->
-    <div :class="['words-box', 'words-box-' + data.property.learn_type]">
-      <div v-for="(item, index) in data.option_list" :key="index" :class="['words-item']"></div>
+    <div :class="['words-box']">
+      <el-image
+        v-if="pic_list[data.option_list[active_index].picture_file_id]"
+        :src="pic_list[data.option_list[active_index].picture_file_id]"
+        fit="contain"
+      />
+      <div class="words-right">
+        <div :class="['words-item']">
+          <label
+            v-for="(item, index) in data.option_list"
+            :key="index"
+            :class="[active_index === index ? 'active' : '']"
+            @click="active_index = index"
+            >{{ item.content }}</label
+          >
+        </div>
+        <template v-for="(item, index) in data.option_list">
+          <div class="strock-box" :key="index" v-if="index === active_index">
+            <div class="strock-left" v-if="item.content_arr_strokes && item.content_arr_strokes.length > 0">
+              <template v-for="(items, indexs) in item.content_arr_strokes">
+                <Strockplayredline
+                  :play-storkes="true"
+                  :book-text="items.hz"
+                  :target-div="'pre' + items.hz + indexs + active_index"
+                  :book-strokes="items.strokes"
+                  :class="['strock-chinese', indexs !== item.content_arr_strokes.length - 1 ? 'border-right-none' : '']"
+                  :key="indexs"
+                  v-if="items"
+                />
+              </template>
+            </div>
+            <div class="strock-right">
+              <el-rate v-model="item.rate" disabled text-color="#ff9900"> </el-rate>
+              <div class="pinyin-box">
+                <AudioPlay :file-id="item.audio_file_id" theme-color="white" v-if="item.audio_file_id" />
+                <span class="pinyin">{{ item.pinyin }}</span>
+              </div>
+            </div>
+          </div>
+        </template>
+        <el-divider></el-divider>
+        <div class="content-box">
+          <p
+            class="definition"
+            v-for="(itemd, indexd) in data.option_list[active_index].definition_preview"
+            :key="indexd"
+          >
+            {{ itemd }}
+          </p>
+          <span
+            class="tips"
+            v-if="
+              data.option_list[active_index].example_sentence[0].trim() ||
+              data.option_list[active_index].example_sentence[1].trim()
+            "
+            >例句:</span
+          >
+          <template v-for="(iteme, indexe) in data.option_list[active_index].example_sentence">
+            <p class="example-sentence" :key="indexe + iteme.trim()" v-if="iteme.trim()">
+              <span>{{ computeOptionMethods[data.option_number_show_mode](indexe) }} </span>
+              <span>{{ iteme }}</span>
+            </p>
+          </template>
+        </div>
+        <el-divider></el-divider>
+        <div class="sound-box">
+          <SoundRecordPreview :wav-blob.sync="answer_list[active_index].audio_file_id" :type="'small'" />
+        </div>
+      </div>
     </div>
   </div>
 </template>
@@ -17,21 +84,21 @@
 <script>
 import { computeOptionMethods } from '@/views/exercise_questions/data/common';
 import PreviewMixin from './components/PreviewMixin';
-import { GetStaticResources } from '@/api/app';
+import { GetStaticResources, GetFileStoreInfo } from '@/api/app';
+import SoundRecordPreview from './components/common/SoundRecordPreview.vue';
+import Strockplayredline from './components/common/Strockplayredline.vue';
 
 export default {
   name: 'WordCardPreview',
-  components: {},
+  components: { SoundRecordPreview, Strockplayredline },
   mixins: [PreviewMixin],
   data() {
     return {
       computeOptionMethods,
       hanzi_color: '#404040', // 描红汉字底色
-      writer_number_yuan: 19,
-      writer_number: null, // 书写个数
-      answer_list: {
-        write_model: {},
-      }, // 用户答题数据
+      pic_list: {},
+      answer_list: [], // 用户答题数据
+      active_index: 0,
     };
   },
   watch: {},
@@ -43,7 +110,44 @@ export default {
   methods: {
     // 初始化数据
     handleData() {
-      this.data.option_list.forEach((item, index) => {});
+      this.answer_list = [];
+      this.pic_list = {};
+      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,
+          audio_file_id: '',
+        };
+        item.definition_preview = item.definition.split('\n');
+        this.answer_list.push(obj);
+        let content_arr = item.content.trim().split('');
+        let content_arrs = [];
+        let content_arr_strokes = [];
+        content_arr.forEach((itemc) => {
+          if (itemc.trim()) {
+            content_arrs.push(itemc.trim());
+          }
+        });
+        content_arrs.forEach((itemc, indexc) => {
+          content_arr_strokes.push(null);
+          let MethodName = 'hz_resource_manager-GetHZStrokesContent';
+          let data = {
+            hz: itemc,
+          };
+          GetStaticResources(MethodName, data).then((res) => {
+            let obj = {
+              hz: itemc.trim(),
+              strokes: res,
+            };
+            content_arr_strokes[indexc] = obj;
+          });
+        });
+        item.content_arr_strokes = content_arr_strokes;
+      });
     },
   },
 };
@@ -52,201 +156,120 @@ export default {
 <style lang="scss" scoped>
 @use '@/styles/mixin.scss' as *;
 
-.chinese-preview {
+.word-card-preview {
   @include preview;
 
   .words-box {
-    .words-item {
-      // display: flex;
-      // flex-wrap: wrap;
-      min-width: 64px;
-      margin-bottom: 24px;
-    }
-
-    .words-top {
-      display: flex;
-      width: 100%;
-      min-height: 30px;
-      border: 1px solid #e81b1b;
-
-      .words-left {
-        box-sizing: border-box;
-        display: flex;
-        column-gap: 4px;
-        align-items: center;
-        justify-content: center;
-        width: 64px;
-        margin-right: 12px;
-        border-right: 1px solid #e81b1b;
-      }
-
-      .words-right {
-        padding: 8px 4px;
-        margin: 0;
-        font-size: 14px;
-        line-height: 14px;
-        color: #000;
-      }
-    }
-
-    .audio-wrapper {
-      height: 16px;
-
-      :deep .audio-play {
-        width: 16px;
-        height: 16px;
-        color: #000;
-        background-color: initial;
-      }
-
-      :deep .audio-play.not-url {
-        color: #a1a1a1;
-      }
+    display: flex;
+    column-gap: 24px;
 
-      :deep .voice-play {
-        width: 16px;
-        height: 16px;
-      }
+    .el-image {
+      flex-shrink: 0;
+      width: 346px;
+      height: 346px;
     }
 
-    .words-info {
-      display: flex;
-      column-gap: 4px;
-      align-items: center;
-      justify-content: center;
-      width: max-content;
-      padding: 5px 16px;
-      margin: 0 auto 8px;
-      font-size: 14px;
-      line-height: 22px;
-      color: #fff;
-      background: #165dff;
-      border-radius: 20px;
-
-      .pinyin {
-        font-size: 14px;
-        color: #fff;
-      }
+    .words-right {
+      flex: 1;
 
-      .audio-wrapper {
-        :deep .audio-play {
+      .words-item {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 16px;
+
+        label {
+          padding: 8px 16px;
+          font-size: 16px;
+          font-weight: 400;
+          line-height: 24px;
           color: #fff;
-        }
+          cursor: pointer;
+          background: rgba(48, 110, 255, 30%);
+          border-radius: 20px;
 
-        :deep .audio-play.not-url {
-          color: #a1a1a1;
+          &.active {
+            background: rgba(48, 110, 255, 100%);
+          }
         }
       }
     }
 
-    .card-box {
-      display: flex;
-      flex-wrap: wrap;
-    }
-
     .pinyin {
       font-family: 'League';
-      font-size: 12px;
+      font-size: 16px;
       font-weight: 500;
-      color: #000;
+      color: #fff;
     }
 
     .strock-chinese {
       border: 1px solid #e81b1b;
-      border-top: none;
-    }
-
-    .strockplay-newWord {
-      position: relative;
-      box-sizing: border-box;
-      flex-shrink: 0;
-      width: 64px;
-      height: 64px;
-      border: 1px solid #e81b1b;
-      border-top: none;
-
-      .character-target-bg,
-      .hanzi-writer-img {
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        color: #dedede;
-      }
-
-      .hanzi-writer-img {
-        z-index: 1;
-      }
-    }
-
-    .border-left-none {
-      border-left: none;
     }
 
     .border-right-none {
       border-right: none;
     }
+  }
 
-    &-learn {
-      display: flex;
-      column-gap: 24px;
-
-      .words-item {
-        display: block;
-      }
-    }
+  .strock-box {
+    display: flex;
+    column-gap: 16px;
+    margin-top: 24px;
   }
 
-  .words-box-dictation {
+  .strock-left {
     display: flex;
-    flex-wrap: wrap;
-    column-gap: 24px;
+  }
 
-    .card-box {
-      display: block;
+  .pinyin-box {
+    display: flex;
+    gap: 4px;
+    align-items: center;
+    width: max-content;
+    padding: 4px 8px;
+    margin-top: 10px;
+    background: rgba(47, 111, 236, 100%);
+    border-radius: 40px;
+
+    :deep .audio-play {
+      width: auto;
+      height: 24px;
+      background: none;
     }
+  }
 
-    .words-dic-box {
-      display: flex;
-      column-gap: 6px;
-      width: max-content;
-      margin: 0 auto;
-    }
+  .definition {
+    margin: 0 0 8px;
+    font-family: 'PingFang SC';
+    font-size: 16px;
+    line-height: 24px;
+    color: #000;
+  }
 
-    .words-dic-item {
-      text-align: center;
+  .tips {
+    display: block;
+    margin-bottom: 8px;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 22px;
+    color: rgba(0, 0, 0, 40%);
+  }
 
-      .pinyin {
-        line-height: 30px;
-      }
-    }
+  .example-sentence {
+    margin: 0;
+    font-size: 14px;
+    line-height: 22px;
+    color: #000;
   }
 
-  .words-dic-item {
-    .strockplay-newWord {
-      border-top: 1px solid #e81b1b;
-    }
+  .sound-box {
+    width: max-content;
+    padding: 4px;
+    background: $content-color;
+    border-radius: 40px;
   }
 
-  .practiceBox {
-    position: fixed;
-    top: 0;
-    left: 0;
-    z-index: 101;
-    box-sizing: border-box;
-    width: 100%;
-    height: 100vh;
-    overflow: hidden;
-    overflow-y: auto;
-    background: rgba(0, 0, 0, 19%);
-
-    &.practice-box-strock {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      padding-top: 0;
-    }
+  .el-divider {
+    margin: 16px 0;
   }
 }
 </style>