Quellcode durchsuchen

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

dusenyao vor 1 Jahr
Ursprung
Commit
7a0fec497b

+ 4 - 0
src/views/exercise_questions/create/components/create.vue

@@ -64,6 +64,8 @@ import WritePictureQuestion from './exercises/WritePictureQuestion.vue';
 import ReplaceAnswerQuestion from './exercises/ReplaceAnswerQuestion.vue';
 import EssayQuestion from './exercises/EssayQuestion.vue';
 import TableFillQuestion from './exercises/TableFillQuestion.vue';
+import FillFormQuestion from './exercises/FillFormQuestion.vue';
+import WordDictationQuestion from './exercises/WordDictationQuestion.vue';
 
 export default {
   name: 'CreateMain',
@@ -113,6 +115,8 @@ export default {
         replace_answer: ReplaceAnswerQuestion,
         essay_question: EssayQuestion,
         table_fill: TableFillQuestion,
+        fill_form: FillFormQuestion,
+        word_dictation: WordDictationQuestion,
       },
     };
   },

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

@@ -20,14 +20,6 @@
             v-loading="loading_list[i] ? loading_list[i].loadings : false"
             class="content-item"
           >
-            <span
-              v-if="data.property.learn_type === 'dictation'"
-              class="question-number"
-              title="双击切换序号类型"
-              @dblclick="changeOptionType(data)"
-            >
-              {{ computedQuestionNumber(i, data.option_number_show_mode) }}
-            </span>
             <el-input v-model="item.content" :placeholder="'输入汉字或词汇'" @blur="handleChineseStrokes(item, i)" />
             <el-input
               v-model="item.pinyin"
@@ -54,16 +46,8 @@
               >
             </div>
             <SoundRecord v-else :wav-blob.sync="item.audio_file_id" />
-            <el-input
-              v-if="data.property.learn_type !== 'dictation'"
-              v-model="item.definition"
-              placeholder="输入释义"
-            />
-            <el-input
-              v-if="data.property.learn_type !== 'dictation'"
-              v-model="item.collocation"
-              placeholder="输入搭配"
-            />
+            <el-input v-model="item.definition" placeholder="输入释义" />
+            <el-input v-model="item.collocation" placeholder="输入搭配" />
             <SvgIcon icon-class="delete" class="delete pointer" @click="deleteOption(i, item.audio_file_id)" />
           </li>
         </ul>
@@ -127,7 +111,7 @@
             {{ label }}
           </el-radio>
         </el-form-item>
-        <el-form-item v-if="data.property.learn_type !== 'dictation'" label="田字格组">
+        <el-form-item label="田字格组">
           <el-input
             v-model="data.property.tian_number"
             type="number"

+ 378 - 0
src/views/exercise_questions/create/components/exercises/WordDictationQuestion.vue

@@ -0,0 +1,378 @@
+<template>
+  <QuestionBase>
+    <template #content>
+      <div class="stem">
+        <RichText v-model="data.stem" :font-size="18" placeholder="输入题干" />
+
+        <RichText
+          v-if="isEnable(data.property.is_enable_description)"
+          v-model="data.description"
+          placeholder="输入提示"
+        />
+      </div>
+
+      <div class="content">
+        <label class="title-little">题目:</label>
+        <ul>
+          <li v-for="(item, i) in data.option_list" :key="i" class="content-item">
+            <span class="question-number" title="双击切换序号类型" @dblclick="changeOptionType(data)">
+              {{ computedQuestionNumber(i, data.option_number_show_mode) }}
+            </span>
+            <el-input v-model="item.content" :placeholder="'输入汉字或词汇'" />
+            <el-input
+              v-model="item.pinyin"
+              :placeholder="'拼音间用空格隔开'"
+              @blur="handleSplitPy(item)"
+              @change="changePinyin(item)"
+            />
+            <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 v-if="item.audio_file_id" :file-id="item.audio_file_id" theme-color="gray" />
+              <span
+                v-loading="loading_list[i] ? loading_list[i].loading : false"
+                class="auto-btn"
+                @click="handleMatically(item, i)"
+                >{{ item.audio_file_id ? '已生成' : '自动生成' }}</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)" />
+          </li>
+        </ul>
+      </div>
+      <div class="footer">
+        <span class="add-option" @click="addOption">
+          <SvgIcon icon-class="add-circle" size="14" /> <span>增加汉字</span>
+        </span>
+      </div>
+    </template>
+
+    <template #property>
+      <el-form :model="data.property">
+        <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-number
+            v-model="data.property.score"
+            :min="0"
+            :step="data.property.score_type === scoreTypeList[0].value ? 1 : 0.1"
+          />
+        </el-form-item>
+        <el-form-item label="音频">
+          <el-radio
+            v-for="{ value, label } in audioGenerationMethodList"
+            :key="value"
+            v-model="data.other.audio_generation_method"
+            :label="value"
+          >
+            {{ label }}
+          </el-radio>
+        </el-form-item>
+      </el-form>
+    </template>
+  </QuestionBase>
+</template>
+
+<script>
+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, addTone } from '@/views/exercise_questions/data/common';
+import { getRandomNumber } from '@/utils/index';
+
+import { wordDictationData, audioGenerationMethodList, getOption } from '@/views/exercise_questions/data/wordDictation';
+
+export default {
+  name: 'WordDictationQuestion',
+  components: {
+    UploadAudio,
+    SoundRecord,
+  },
+  mixins: [QuestionMixin],
+  data() {
+    return {
+      audioGenerationMethodList,
+      data: JSON.parse(JSON.stringify(wordDictationData)),
+      changeOptionType,
+      handleInputNumber,
+      loading_list: [
+        {
+          loading: false,
+        },
+        {
+          loading: false,
+        },
+        {
+          loading: false,
+        },
+      ],
+      matically_pinyin_obj: {}, // 存放转成声调的拼音
+    };
+  },
+  watch: {
+    'data.option_list.length': {
+      handler(val) {
+        this.loading_list = [];
+        for (let i = 0; i < val; i++) {
+          let obj = {
+            loading: false,
+          };
+          this.loading_list.push(obj);
+        }
+      },
+      deep: true,
+      immediate: true,
+    },
+  },
+  mounted() {},
+  methods: {
+    addOption() {
+      this.data.option_list.push(getOption());
+      this.loading_list.push({
+        loading: false,
+      });
+    },
+    uploads(file_id, index) {
+      this.data.option_list[index].audio_file_id = file_id;
+      this.data.file_id_list.push(file_id);
+    },
+    deleteFiles(file_id, itemIndex) {
+      this.data.option_list[itemIndex].audio_file_id = '';
+      this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
+    },
+    // 删除小题
+    deleteOption(i, file_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.loading_list.splice(i, 1);
+        })
+        .catch(() => {});
+    },
+    // 自动生成音频
+    handleMatically(item, i) {
+      if (item.pinyin.trim()) {
+        this.loading_list[i].loading = true;
+        let MethodName = 'tool-PinyinToVoiceFile';
+        let data = {
+          pinyin: item.pinyin.trim().split(' ').join(','),
+        };
+        GetStaticResources(MethodName, data)
+          .then((res) => {
+            this.loading_list[i].loading = false;
+            if (res.status === 1) {
+              item.audio_file_id = res.file_id;
+              this.data.file_id_list.push(res.file_id);
+            }
+          })
+          .catch(() => {
+            this.loading_list[i].loading = false;
+          });
+      }
+    },
+
+    // 切割拼音
+    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) => {
+        let obj = {
+          pinyin_item: itemp,
+        };
+        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') {
+        item.audio_file_id = '';
+      }
+    },
+    /**
+     * 智能识别
+     * @param {String} text 识别数据
+     */
+    recognition(text) {
+      let arr = text
+        .split(/[\r\n]/)
+        .map((item) => item.trim())
+        .filter((item) => item);
+
+      if (arr.length > 0) {
+        this.data.stem = arr[0];
+        this.data.option_list = [];
+        arr.slice(1).map((content, index) => {
+          let content_item = content.split('&&');
+          this.data.option_list.push({
+            content: content_item[0] ? content_item[0] : '',
+            mark: getRandomNumber(),
+            audio_file_id: '',
+            pinyin: content_item[1] ? content_item[1] : '',
+            pinyin_item_list: [],
+          });
+          this.handleSplitPy(this.data.option_list[index]);
+        });
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.content {
+  display: flex;
+  flex-direction: column;
+
+  .content-item {
+    .upload-wrapper {
+      margin-top: 0;
+    }
+
+    :deep .file-name {
+      width: 205px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .auto-matically {
+      display: flex;
+      flex-shrink: 0;
+      align-items: center;
+      width: 233px;
+      padding: 5px 12px;
+      background-color: $fill-color;
+      border-radius: 2px;
+
+      .audio-wrapper {
+        margin-right: 12px;
+
+        :deep .audio-play {
+          width: 16px;
+          height: 16px;
+          color: #000;
+          background-color: initial;
+        }
+
+        :deep .audio-play.not-url {
+          color: #a1a1a1;
+        }
+
+        :deep .voice-play {
+          width: 16px;
+          height: 16px;
+        }
+      }
+
+      .auto-btn {
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 22px;
+        color: #1d2129;
+        cursor: pointer;
+      }
+    }
+
+    .delete {
+      flex-shrink: 0;
+      width: 16px;
+      height: 16px;
+    }
+  }
+}
+</style>

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

@@ -22,6 +22,7 @@ import WritePictruePreview from '../preview/WritePictruePreview.vue';
 import ReplaceAnswerPreview from '../preview/ReplaceAnswerPreview.vue';
 import EssayQuestionPreview from '../preview/EssayQuestionPreview.vue';
 import TableFillPreview from '../preview/TableFillPreview.vue';
+import WordDictationPreview from '../preview/WordDictationPreview.vue';
 
 const PreviewQuestionTypeMixin = {
   data() {
@@ -50,6 +51,7 @@ const PreviewQuestionTypeMixin = {
         replace_answer: ReplaceAnswerPreview,
         essay_question: EssayQuestionPreview,
         table_fill: TableFillPreview,
+        word_dictation: WordDictationPreview,
       },
     };
   },

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

@@ -1,4 +1,4 @@
-import { stemTypeList, scoreTypeList, questionNumberTypeList, optionTypeList, switchOption } from './common';
+import { stemTypeList, scoreTypeList, questionNumberTypeList, switchOption } from './common';
 import { getRandomNumber } from '@/utils/index';
 
 export function getOption(content = '') {
@@ -17,7 +17,6 @@ export function getOption(content = '') {
 export const learnTypeList = [
   { value: 'paint', label: '描红' },
   { value: 'write', label: '书写' },
-  { value: 'dictation', label: '听写' },
 ];
 
 // 音频生成方式类型
@@ -41,7 +40,6 @@ export const chineseData = {
   type: 'chinese', // 题型
   stem: '', // 题干
   description: '', // 描述
-  option_number_show_mode: optionTypeList[1].value, // 选项类型
   answer: { score: 1, score_type: scoreTypeList[0].value }, // 答案
   option_list: [getOption(), getOption(), getOption()], // 选项
   file_id_list: [],

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

@@ -20,6 +20,8 @@ import { writeData } from './write';
 import { writePictrueData } from './writePicture';
 import { readData } from './read';
 import { getTableFillData } from './tableFill';
+import { getFillFormData } from './fillForm';
+import { wordDictationData } from './wordDictation';
 
 // 题型源数据
 export const questionTypeDataOption = [
@@ -64,6 +66,7 @@ export const questionTypeDataOption = [
     label: '汉字题',
     children: [
       { label: '汉字练习', value: 'chinese', data: chineseData },
+      { label: '字词听写', value: 'word_dictation', data: wordDictationData },
       { label: '字词卡片', value: 'word_card', data: wordCardData },
     ],
   },

+ 51 - 0
src/views/exercise_questions/data/wordDictation.js

@@ -0,0 +1,51 @@
+import { stemTypeList, scoreTypeList, questionNumberTypeList, optionTypeList, switchOption } from './common';
+import { getRandomNumber } from '@/utils/index';
+
+export function getOption(content = '') {
+  return {
+    content,
+    mark: getRandomNumber(),
+    audio_file_id: '',
+    pinyin: '',
+    pinyin_item_list: [],
+  };
+}
+// 音频生成方式类型
+export const audioGenerationMethodList = [
+  {
+    value: 'upload',
+    label: '上传',
+  },
+  {
+    value: 'auto',
+    label: '自动生成',
+  },
+  {
+    value: 'record',
+    label: '录音',
+  },
+];
+
+// 字词听写数据模板
+export const wordDictationData = {
+  type: 'word_dictation', // 题型
+  stem: '', // 题干
+  description: '', // 描述
+  option_number_show_mode: optionTypeList[1].value, // 选项类型
+  answer: { score: 1, score_type: scoreTypeList[0].value }, // 答案
+  option_list: [getOption(), getOption(), getOption()], // 选项
+  file_id_list: [],
+  // 题型属性
+  property: {
+    stem_type: stemTypeList[1].value, // 题干类型
+    question_number: '1', // 题号
+    is_enable_description: switchOption[0].value, // 描述
+    score: 1, // 分值
+    score_type: scoreTypeList[0].value, // 分值类型
+  },
+  // 其他属性
+  other: {
+    question_number_type: questionNumberTypeList[1].value, // 题号类型
+    audio_generation_method: audioGenerationMethodList[0].value, // 音频生成方式
+  },
+};

+ 3 - 71
src/views/exercise_questions/preview/ChinesePreview.vue

@@ -18,7 +18,6 @@
           v-if="item.content && item.content.trim() && item.hz_strokes_list[0] && item.hz_strokes_list[0].strokes"
         >
           <div
-            v-if="data.property.learn_type !== 'dictation'"
             class="words-top"
             :style="{
               width:
@@ -101,30 +100,6 @@
             </div>
           </div>
         </template>
-        <div v-if="data.property.learn_type === 'dictation'" class="card-box">
-          <div class="words-info">
-            <span>{{ computeOptionMethods[data.option_number_show_mode](index) }} </span>
-            <span class="pinyin">{{ item.pinyin }}</span>
-            <AudioPlay :file-id="item.audio_file_id" theme-color="white" />
-          </div>
-          <div class="words-dic-box">
-            <div v-for="(itemc, indexc) in item.imgArr" :key="indexc" class="words-dic-item">
-              <span class="pinyin">{{ item.pinyin_arr[indexc].pinyin_item }}</span>
-              <div
-                :class="['strockplay-newWord']"
-                @click="freeWrite(itemc ? JSON.parse(itemc) : itemc, index, indexc, item.mark)"
-              >
-                <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" />
-                <img
-                  v-if="!play_status && itemc && JSON.parse(itemc).strokes_image"
-                  class="hanzi-writer-img"
-                  :src="JSON.parse(itemc).strokes_image"
-                  alt=""
-                />
-              </div>
-            </div>
-          </div>
-        </div>
       </div>
     </div>
     <div v-if="if_free_show" class="practiceBox practice-box-strock">
@@ -262,22 +237,7 @@ export default {
       option_list.forEach((item, index) => {
         let arr = [];
         this.miao_arr.push([]);
-        if (this.data.property.learn_type === 'dictation') {
-          item.pinyin_arr = [];
-          let pinyin_arr = item.pinyin.trim().split(' ');
-          pinyin_arr.forEach((itemp) => {
-            let obj = {
-              pinyin_item: itemp,
-            };
-            arr.push(null);
-            item.pinyin_arr.push(obj);
-          });
-          if (this.isJudgingRightWrong) {
-            item.imgArr = this.answer.answer_list[index].strokes_content_list;
-          } else {
-            item.imgArr = arr;
-          }
-        } else if (item.content.trim()) {
+        if (item.content.trim()) {
           for (let i = 0; i < this.writer_number; i++) {
             item.hz_strokes_list.forEach((items) => {
               if (this.data.property.learn_type === 'paint') {
@@ -327,11 +287,8 @@ export default {
       this.active_index = index;
       this.active_col_index = indexs;
       this.active_mark = mark;
-      if (this.data.property.learn_type === 'dictation') {
-        this.current_hz = this.hz_data[index];
-      } else {
-        this.current_hz = this.option_list[index].content;
-      }
+
+      this.current_hz = this.option_list[index].content;
       this.current_hz_data = imgUrl;
     },
     // 删除记录
@@ -506,31 +463,6 @@ export default {
     }
   }
 
-  .words-box-dictation {
-    display: flex;
-    flex-wrap: wrap;
-    column-gap: 24px;
-
-    .card-box {
-      display: block;
-    }
-
-    .words-dic-box {
-      display: flex;
-      column-gap: 6px;
-      width: max-content;
-      margin: 0 auto;
-    }
-
-    .words-dic-item {
-      text-align: center;
-
-      .pinyin {
-        line-height: 30px;
-      }
-    }
-  }
-
   .words-dic-item {
     .strockplay-newWord {
       border-top: 1px solid #e81b1b;

+ 429 - 0
src/views/exercise_questions/preview/WordDictationPreview.vue

@@ -0,0 +1,429 @@
+<!-- eslint-disable vue/no-v-html -->
+<template>
+  <div v-if="show_preview" class="chinese-preview">
+    <div class="stem">
+      <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 rich-text"
+      v-html="sanitizeHTML(data.description)"
+    ></div>
+
+    <!-- 笔画学习 -->
+    <div :class="['words-box', 'words-box-dictation']">
+      <div v-for="(item, index) in option_list" :key="index" :class="['words-item']">
+        <div class="card-box">
+          <div class="words-info">
+            <span>{{ computeOptionMethods[data.option_number_show_mode](index) }} </span>
+            <span class="pinyin">{{ item.pinyin }}</span>
+            <AudioPlay :file-id="item.audio_file_id" theme-color="white" />
+          </div>
+          <div class="words-dic-box">
+            <div v-for="(itemc, indexc) in item.imgArr" :key="indexc" class="words-dic-item">
+              <span class="pinyin">{{ item.pinyin_arr[indexc].pinyin_item }}</span>
+              <div
+                :class="['strockplay-newWord']"
+                @click="freeWrite(itemc ? JSON.parse(itemc) : itemc, index, indexc, item.mark)"
+              >
+                <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" />
+                <img
+                  v-if="!play_status && itemc && JSON.parse(itemc).strokes_image"
+                  class="hanzi-writer-img"
+                  :src="JSON.parse(itemc).strokes_image"
+                  alt=""
+                />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div v-if="if_free_show" class="practiceBox practice-box-strock">
+      <FreewriteLettle
+        ref="freePaint"
+        :current-tree-i-d="'1234456'"
+        :current-hz="current_hz"
+        :curren-hz-data="current_hz_data"
+        :row-index="active_index"
+        :col-index="active_col_index"
+        :disabled="disabled"
+        @closeIfFreeShow="closeIfFreeShow"
+        @changePraShow="changePraShow"
+        @changeCurQue="changeCurQue"
+        @deleteWriteRecord="deleteWriteRecord"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import { computeOptionMethods } from '@/views/exercise_questions/data/common';
+import PreviewMixin from './components/PreviewMixin';
+import FreewriteLettle from './components/common/FreewriteLettle.vue';
+
+export default {
+  name: 'WordDictationPreview',
+  components: {
+    FreewriteLettle,
+  },
+  mixins: [PreviewMixin],
+  data() {
+    return {
+      computeOptionMethods,
+      hanzi_color: '#404040', // 描红汉字底色
+      if_free_show: false,
+      free_img: [],
+      active_index: null,
+      active_col_index: null,
+      current_hz: '', // 当前汉字
+      current_hz_data: null, // 当前汉字数据
+      play_status: false, // 播放状态
+      hz_data: [
+        '你',
+        '最',
+        '近',
+        '怎',
+        '么',
+        '样',
+        '我',
+        '很',
+        '好',
+        '再',
+        '见',
+        '吃',
+        '饭',
+        '天',
+        '启',
+        '扫',
+        '描',
+        '以',
+        '平',
+        '太',
+        '效',
+        '国',
+        '是',
+        '称',
+        '需',
+        '值',
+        '复',
+        '包',
+        '头',
+        '条',
+        '够',
+        '关',
+        '放',
+        '发',
+        '补',
+        '快',
+        '素',
+      ],
+      active_mark: '',
+      option_list: [],
+      show_preview: false,
+      miao_arr: [],
+    };
+  },
+  watch: {
+    data: {
+      handler(val) {
+        if (!val || this.data.type !== 'word_dictation') return;
+        this.handleData();
+      },
+      deep: true,
+      immediate: true,
+    },
+    isJudgingRightWrong: {
+      handler(val) {
+        if (!val) return;
+        this.handleData();
+      },
+      immediate: true,
+    },
+  },
+  created() {
+    // this.handleData();
+  },
+  mounted() {},
+  methods: {
+    // 初始化数据
+    handleData() {
+      if (!this.isJudgingRightWrong) {
+        this.answer.answer_list = [];
+      }
+      this.show_preview = false;
+      let option_list = JSON.parse(JSON.stringify(this.data)).option_list;
+
+      this.miao_arr = [];
+      option_list.forEach((item, index) => {
+        let arr = [];
+        this.miao_arr.push([]);
+        item.pinyin_arr = [];
+        let pinyin_arr = item.pinyin.trim().split(' ');
+        pinyin_arr.forEach((itemp) => {
+          let obj = {
+            pinyin_item: itemp,
+          };
+          arr.push(null);
+          item.pinyin_arr.push(obj);
+        });
+        if (this.isJudgingRightWrong) {
+          item.imgArr = this.answer.answer_list[index].strokes_content_list;
+        } else {
+          item.imgArr = arr;
+        }
+
+        let obj = {
+          mark: item.mark,
+          strokes_content_list: arr,
+        };
+        if (!this.isJudgingRightWrong) {
+          this.answer.answer_list.push(obj);
+        }
+      });
+      this.option_list = option_list;
+      this.show_preview = true;
+    },
+    changePraShow() {
+      this.if_free_show = false;
+    },
+    closeIfFreeShow(data, rowIndex, colIndex, mark) {
+      this.option_list[rowIndex].imgArr[colIndex] = JSON.stringify(data);
+      this.if_free_show = false;
+      this.freeWrite(data, rowIndex, colIndex, mark);
+      this.$forceUpdate();
+    },
+    freeWrite(imgUrl, index, indexs, mark) {
+      this.if_free_show = true;
+      this.active_index = index;
+      this.active_col_index = indexs;
+      this.active_mark = mark;
+      this.current_hz = this.hz_data[index];
+
+      this.current_hz_data = imgUrl;
+    },
+    // 删除记录
+    deleteWriteRecord(rowIndex, colIndex) {
+      this.$set(this.option_list[rowIndex].imgArr, colIndex, JSON.stringify({}));
+      this.changeCurQue(null, colIndex, this.active_mark);
+      this.current_hz_data = null;
+      this.active_mark = '';
+      this.$forceUpdate();
+    },
+    changeCurQue(answer, colIndex, mark) {
+      if (answer) {
+        let write_model = [];
+        this.answer.answer_list.forEach((itema) => {
+          if (itema.mark === mark) {
+            write_model = itema.strokes_content_list;
+          }
+        });
+        write_model[colIndex] = JSON.stringify(answer);
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.chinese-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;
+      }
+
+      :deep .voice-play {
+        width: 16px;
+        height: 16px;
+      }
+    }
+
+    .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;
+      }
+
+      .audio-wrapper {
+        :deep .audio-play {
+          color: #fff;
+        }
+
+        :deep .audio-play.not-url {
+          color: #a1a1a1;
+        }
+      }
+    }
+
+    .card-box {
+      display: flex;
+      flex-wrap: wrap;
+    }
+
+    .pinyin {
+      font-family: 'League';
+      font-size: 12px;
+      font-weight: 500;
+      color: #000;
+    }
+
+    .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;
+      }
+    }
+  }
+
+  .words-box-dictation {
+    display: flex;
+    flex-wrap: wrap;
+    column-gap: 24px;
+
+    .card-box {
+      display: block;
+    }
+
+    .words-dic-box {
+      display: flex;
+      column-gap: 6px;
+      width: max-content;
+      margin: 0 auto;
+    }
+
+    .words-dic-item {
+      text-align: center;
+
+      .pinyin {
+        line-height: 30px;
+      }
+    }
+  }
+
+  .words-dic-item {
+    .strockplay-newWord {
+      border-top: 1px solid #e81b1b;
+    }
+  }
+
+  .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;
+    }
+  }
+}
+</style>