Explorar o código

生字组件 描红预览

natasha hai 1 semana
pai
achega
67c7f67aeb

+ 123 - 28
src/views/book/courseware/preview/components/newWord_template/NewWordTemplatePreview.vue

@@ -5,31 +5,49 @@
 
     <div class="main">
       <div class="item-box" v-for="(item, index) in data.option_list" :key="index">
-        <div class="number-box">
+        <div class="number-box" :style="{ marginTop: isEnable(data.property.is_enable_pinyin) ? '30px' : '' }">
           <span class="number">{{ index + 1 }}</span>
         </div>
-        <div class="items" v-for="(items, indexs) in item.content_list" :key="indexs">
-          <template v-if="items && items.type === 'img'">
-            <el-image
-              class="items-image"
-              v-if="items.file_list[0]"
-              :src="items.file_list[0].file_url"
-              fit="contain"
-            ></el-image>
-          </template>
-          <template v-else-if="items && items.type === 'lian'">
-            <span class="items-lian">{{ items.con }}</span>
-          </template>
-          <Strockplayredline
-            v-else-if="items && items.type === 'hanzi'"
-            :Book_text="items.con"
-            :playStorkes="isEnable(data.property.is_enable_play_structure)"
-            :curItem="isEnable(data.property.is_enable_high_strokes) ? items : null"
-            :type="data.type"
-            :targetDiv="'newWordTemplate' + index + indexs"
-            :hz_json="items.hz_info[0].hzDetail.hz_json"
-            class="hanzi-storck"
-          />
+        <div
+          class="items"
+          :class="[items.is_example ? 'items-example' : '']"
+          v-for="(items, indexs) in item.content_list"
+          :key="indexs"
+        >
+          <div v-if="isEnable(data.property.is_enable_pinyin)" class="pinyin">{{ items.pinyin }}</div>
+          <div class="items-content">
+            <template v-if="items && items.type === 'img'">
+              <el-image
+                class="items-image"
+                v-if="items.file_list[0]"
+                :src="items.file_list[0].file_url"
+                fit="contain"
+              ></el-image>
+            </template>
+            <template v-else-if="items && items.type === 'lian'">
+              <span class="items-lian">{{ items.con }}</span>
+            </template>
+            <Strockplayredline
+              v-if="items && items.type === 'hanzi'"
+              :Book_text="items.con"
+              :playStorkes="isEnable(data.property.is_enable_play_structure)"
+              :curItem="isEnable(data.property.is_enable_high_strokes) ? userAnswer[index][indexs] : null"
+              :type="data.type"
+              :targetDiv="'newWordTemplate' + items.con + index + indexs"
+              :hz_json="items.hz_info[0].hzDetail.hz_json"
+              class="hanzi-storck"
+            />
+          </div>
+          <div class="inputdv">
+            <EditDiv
+              v-if="items && items.type === 'hanzi' && items.is_can_input_answer"
+              :id="'a' + items.con + index + indexs"
+              :canEdit="!items.is_example"
+              v-model="userAnswer[index][indexs].answer"
+              :textAlign="'center'"
+              @input="changeAnswer(items, index, indexs)"
+            />
+          </div>
         </div>
       </div>
     </div>
@@ -41,27 +59,71 @@ import { getNewWordTemplateData } from '@/views/book/courseware/data/newWordTemp
 
 import PreviewMixin from '../common/PreviewMixin';
 import Strockplayredline from './components/Strockplayredline.vue';
+import EditDiv from './components/EditDiv.vue';
 export default {
   name: 'NewWordPreview',
 
-  components: { Strockplayredline },
+  components: { Strockplayredline, EditDiv },
   mixins: [PreviewMixin],
   data() {
     return {
       data: getNewWordTemplateData(),
+      userAnswer: [],
     };
   },
   watch: {
-    data: {
+    'data.option_list': {
       handler(val) {
         if (val) {
+          this.handleData();
         }
       },
       deep: true,
       immediate: true,
     },
   },
-  methods: {},
+  methods: {
+    // 修改数字高亮对应笔画(待优化)
+    changeAnswer(items, col, row) {
+      // items.isShow = false;
+      setTimeout(() => {
+        // items.isShow = true;
+        // if (items.answer) {
+        //   if (items.answer == this.curQue.Bookanswer[col][row].answer) {
+        //     this.curQue.Bookanswer[col][row].userAnswerJudge =
+        //       "[JUDGE##T##JUDGE]";
+        //   } else if (items.answer != this.curQue.Bookanswer[col][row].answer) {
+        //     this.curQue.Bookanswer[col][row].userAnswerJudge =
+        //       "[JUDGE##F##JUDGE]";
+        //   }
+        // }
+        this.$forceUpdate();
+      }, 10);
+    },
+    handleData() {
+      let answer_list = [];
+      this.data.option_list.forEach((item) => {
+        let arr = [];
+        item.content_list.forEach((items) => {
+          if (items.is_example) {
+            arr.push({
+              answer: items.answer,
+              answer_pinyin: items.answer_pinyin,
+              answer_en: items.answer_en,
+            });
+          } else {
+            arr.push({
+              answer: '',
+              answer_pinyin: '',
+              answer_en: '',
+            });
+          }
+        });
+        answer_list.push(arr);
+      });
+      this.userAnswer = answer_list;
+    },
+  },
 };
 </script>
 
@@ -69,9 +131,15 @@ export default {
 @use '@/styles/mixin.scss' as *;
 
 .newWord-template-preview {
+  .main {
+    display: flex;
+    flex-flow: wrap;
+    gap: 30px;
+  }
+
   .item-box {
     display: flex;
-    align-items: end;
+    gap: 5px;
   }
 
   .number-box {
@@ -99,6 +167,16 @@ export default {
     font-size: 0;
   }
 
+  .pinyin {
+    height: 30px;
+    min-height: 16px;
+    font-family: 'League';
+    font-size: 20px;
+    font-weight: 400;
+    color: rgba(0, 0, 0, 50%);
+    text-align: center;
+  }
+
   .items-image,
   .hanzi-storck {
     width: 80px;
@@ -117,7 +195,24 @@ export default {
     color: #346cda;
   }
 
-  .hanzi-storck {
+  .inputdv {
+    min-height: 32px;
+    margin-top: 16px;
+
+    :deep .edit-div {
+      min-height: 32px;
+      font-size: 16px;
+      line-height: 32px;
+      color: #000;
+      background-color: #deebff;
+      border-radius: 8px;
+    }
+  }
+
+  .items-example {
+    :deep .edit-div {
+      color: #346cda;
+    }
   }
 }
 </style>

+ 311 - 0
src/views/book/courseware/preview/components/newWord_template/components/EditDiv.vue

@@ -0,0 +1,311 @@
+<template>
+  <div>
+    <div
+      :id="id"
+      class="edit-div"
+      v-html="content"
+      :contenteditable="canEdit"
+      @compositionstart="compositStart"
+      @compositionend="compositEnd"
+      @paste="copyText"
+      @focus="isLocked = true"
+      @blur="handleBlur"
+      @input="input"
+      @keyup.enter="handleReplaceTone"
+      :style="{ textAlign: textAlign }"
+      placeholder=" "
+    ></div>
+    <!-- :placeholder="placeholder_value" -->
+    <div class="limit" v-if="isShow">{{ textSize }}/{{ maxLength }}</div>
+  </div>
+</template>
+<script>
+export default {
+  name: 'editDiv',
+  props: {
+    value: {
+      type: String,
+      default: '',
+    },
+    canEdit: {
+      type: Boolean,
+      default: true,
+    },
+    placeholder: {
+      type: String,
+      default: '',
+    },
+    id: {
+      type: String,
+      default: 'sendMsg',
+    },
+    textAlign: {
+      type: String,
+      default: 'left',
+    },
+    isShow: {
+      type: Boolean,
+      default: false,
+    },
+    maxLength: {
+      type: Number,
+      default: 200,
+    },
+    hengIndex: {
+      type: Number,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      isLocked: false,
+      textSize: 0,
+      lock: true,
+      fullContent: '',
+      content: this.value, // 文本数据
+      tableData: [
+        ['ā', 'á', 'ǎ', 'à', 'a'],
+        ['ō', 'ó', 'ǒ', 'ò', 'o'],
+        ['ē', 'é', 'ě', 'è', 'e'],
+        ['ī', 'í', 'ǐ', 'ì', 'i'],
+        ['ū', 'ú', 'ǔ', 'ù', 'u'],
+        ['ǖ', 'ǘ', 'ǚ', 'ǜ', 'ü'],
+        ['Ā', 'Á', 'Â', 'À', 'A'],
+        ['Ō', 'Ó', 'Ô', 'Ò', 'O'],
+        ['Ē', 'É', 'Ê', 'È', 'E'],
+        ['Ī', 'Í', 'Î', 'Ì', 'I'],
+        ['Ū', 'Ú', 'Û', 'Ù', 'U'],
+      ],
+    };
+  },
+  watch: {
+    value() {
+      if (!this.isLocked && !this.content) {
+        this.content = this.value;
+      }
+    },
+  },
+  computed: {
+    placeholder_value() {
+      let value = '';
+      let _this = this;
+      if (_this.placeholder == 'place_en') {
+        value = '';
+      } else {
+        value = '';
+      }
+      if (_this.textAlign == 'left' || !_this.textAlign) {
+        value = ' ' + value;
+      } else if (_this.textAlign == 'center') {
+        value = value;
+      } else if (_this.textAlign == 'right') {
+        value = value + ' ';
+      }
+      return value;
+    },
+  },
+  methods: {
+    // 输入中文开始时
+    compositStart(e) {
+      this.lock = false;
+      let id = '#' + this.id;
+      document.querySelector(id).focus();
+      document.execCommand('selectAll', false, null);
+      document.getSelection().collapseToEnd();
+    },
+    // 输入中文结束时
+    compositEnd(e) {
+      this.lock = true;
+      this.input(e);
+    },
+    input(e) {
+      if (this.lock) {
+        this.textSize = e.target.innerHTML.length;
+        if (this.textSize >= this.maxLength) {
+          this.textSize = this.maxLength;
+          this.fullContent = e.target.innerHTML.substring(0, this.maxLength);
+          e.target.innerHTML = e.target.innerHTML.substring(0, this.maxLength);
+        } else {
+          this.fullContent = '';
+        }
+        this.$emit('input', e.target.innerHTML);
+        //this.content = e.target.innerHTML;
+        this.textFocus();
+      } else if (this.fullContent) {
+        // 目标对象:超过200字时候的中文输入法
+        // 原由:虽然不会输入成功,但是输入过程中字母依然会显现在输入框内
+        // 弊端:谷歌浏览器输入法的界面偶尔会闪现
+        e.target.innerHTML = this.fullContent;
+        this.lock = true;
+        this.textFocus();
+      }
+      this.$emit('saveBlankTF', e.target.innerHTML, this.hengIndex);
+    },
+    // 粘贴富文本转为纯文本
+    copyText(e) {
+      e.stopPropagation();
+      e.preventDefault();
+      let text = '',
+        event = e.originalEvent || e;
+      if (event.clipboardData && event.clipboardData.getData) {
+        text = event.clipboardData.getData('text/plain');
+      } else if (window.clipboardData && window.clipboardData.getData) {
+        text = window.clipboardData.getData('Text');
+      }
+      if (document.queryCommandSupported('insertText')) {
+        document.execCommand('insertText', false, text);
+      } else {
+        document.execCommand('paste', false, text);
+      }
+    },
+    // 文本输入框聚焦,焦点在最后位置
+    textFocus() {
+      let _this = this;
+      setTimeout(() => {
+        let id = '#' + _this.id;
+        document.querySelector(id).focus();
+        document.execCommand('selectAll', false, null);
+        document.getSelection().collapseToEnd();
+      }, 0);
+    },
+    handleReplaceTone(e) {
+      let _this = this;
+      _this.$nextTick(() => {
+        let value = e.target.innerHTML;
+        _this.resArr = [];
+        if (value) {
+          let reg = /\s+/g;
+          let valueArr = value.split(reg);
+          valueArr.forEach((item, index) => {
+            this.handleValue(item);
+          });
+          let str = '';
+          setTimeout(() => {
+            _this.resArr.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 + ' ';
+                  }
+                }
+              });
+            });
+            e.target.innerHTML = str.trim();
+            _this.fullContent = str.trim();
+            _this.$emit('input', str.trim());
+            _this.textFocus();
+          }, 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: con,
+              isTran: true,
+            };
+            numList.push(obj);
+          }
+        }
+      } else {
+        numList = [];
+      }
+      if (numList.length == 0) {
+        this.resArr.push([{ con: valItem }]);
+      } else {
+        this.resArr.push(numList);
+      }
+    },
+    addTone(number, con) {
+      let _this = this;
+      let zmList = ['a', 'o', 'e', 'i', 'u', 'v', 'A', 'O', 'E', 'I', 'U'];
+      if (number) {
+        for (let i = 0; i < zmList.length; i++) {
+          let zm = zmList[i];
+          if (con.indexOf(zm) > -1) {
+            let zm2 = _this.tableData[i][number - 1];
+            if (con.indexOf('iu') > -1) {
+              zm2 = _this.tableData[4][number - 1];
+              con = con.replace('u', zm2);
+            } else if (con.indexOf('ui') > -1) {
+              zm2 = _this.tableData[3][number - 1];
+              con = con.replace('i', zm2);
+            } else if (
+              con.indexOf('yv') > -1 ||
+              con.indexOf('jv') > -1 ||
+              con.indexOf('qv') > -1 ||
+              con.indexOf('xv') > -1
+            ) {
+              zm2 = _this.tableData[4][number - 1];
+              con = con.replace('v', zm2);
+            } else {
+              con = con.replace(zm, zm2);
+            }
+
+            break;
+          }
+        }
+      }
+      return con;
+    },
+    handleBlur(e) {
+      this.isLocked = false;
+      e.target.innerHTML = e.target.innerHTML.replace(/&nbsp;/gi, '').trim();
+      this.$emit('input', e.target.innerHTML);
+    },
+  },
+  mounted() {
+    if (this.content) {
+      this.textSize = this.content.length;
+    }
+  },
+};
+</script>
+<style lang="scss" rel="stylesheet/scss">
+.edit-div {
+  width: 100%;
+  // height: 100%; 不可加
+  overflow: auto;
+  word-break: break-all;
+  outline: none;
+  user-select: text;
+  white-space: pre-wrap;
+  text-align: left;
+  &[contenteditable='true'] {
+    user-modify: read-write-plaintext-only;
+    &:empty:before {
+      content: attr(placeholder);
+      display: block;
+      color: #ccc;
+    }
+  }
+}
+.limit {
+  position: absolute;
+  right: 0px;
+  bottom: 0px;
+  font-size: 16px;
+  line-height: 24px;
+  color: rgba(0, 0, 0, 0.45);
+}
+</style>

+ 12 - 1
src/views/book/courseware/preview/components/newWord_template/components/Strockplayredline.vue

@@ -58,7 +58,7 @@ export default {
   watch: {
     targetDiv: {
       handler: function (val, oldVal) {
-        if (val != oldVal) {
+        if (val !== oldVal) {
           let _this = this;
           _this.$nextTick(() => {
             _this.initHanziwrite();
@@ -68,6 +68,17 @@ export default {
       // 深度观察监听
       deep: true,
     },
+    'curItem.answer': {
+      handler: function (val, oldVal) {
+        document.getElementById(this.targetDiv).innerHTML = '';
+        let _this = this;
+        _this.$nextTick(() => {
+          _this.initHanziwrite();
+        });
+      },
+      // 深度观察监听
+      deep: true,
+    },
   },
   //方法集合
   methods: {