natasha 1 rok temu
rodzic
commit
85dc7861e7

+ 19 - 1
src/api/api.js

@@ -449,4 +449,22 @@ export function postapi(data) {
         data: data.data,
     });
 }
-// 子登录
+export function GetFileURLMap (data) {
+  let userInfor = getToken();
+    let JSESSIONID = '';
+    let SessionID = '';
+    let UserCode = '';
+    let UserType = '';
+    if (userInfor) {
+        let user = JSON.parse(getToken());
+        UserCode = user.user_code;
+        UserType = user.user_type;
+        SessionID = user.session_id;
+        JSESSIONID = user['JSESSIONID'];
+    }
+    return request({
+        url: `/GCLSFileServer/ServiceInterface?MethodName=file_store_manager-GetFileURLMap&UserCode=${UserCode}&UserType=${UserType}&SessionID=${SessionID}`,
+        method: 'post',
+        data: data,
+    });
+}

BIN
src/assets/teacherdev/icon-voice-play-red.png


BIN
src/assets/teacherdev/icon-voice-red.png


+ 13 - 0
src/components/corpus/StrockplayredlineTable.vue

@@ -47,6 +47,7 @@ export default {
     'strokeNumber',
     'targetDivGray',
     'BoxbgType',
+    'fontSize',
   ],
   data() {
     return {
@@ -79,6 +80,18 @@ export default {
       // 深度观察监听
       deep: true,
     },
+    fontSize: {
+      handler: function (val, oldVal) {
+        if (val != oldVal) {
+          let _this = this;
+          _this.$nextTick(() => {
+            _this.initHanziwrite();
+          });
+        }
+      },
+      // 深度观察监听
+      deep: true,
+    },
   },
   //方法集合
   methods: {

+ 27 - 41
src/components/corpus/Strockred.vue

@@ -5,47 +5,25 @@
       <div @click="resetHanzi" class="strock-play-box" v-if="1 == 2">
         <!-- <img src="../../assets/common/strock-play.png" class="strock-play" /> -->
       </div>
-      <div
-        :class="wordNum == '2' ? 'character-target-div_220' : ''"
-        :id="targetDiv"
-        class="character-target-div"
-      >
-        <svg-icon
-          icon-class="tian"
-          className="tian-bg"
-          v-if="BoxbgType == 0"
-          :style="{ color: '#DEDEDE' }"
-        />
-        <svg-icon
-          icon-class="mi"
-          className="tian-bg"
-          v-if="BoxbgType == 1"
-          :style="{ color: '#DEDEDE' }"
-        />
+      <div :class="wordNum == '2' ? 'character-target-div_220' : ''" :id="targetDiv" class="character-target-div">
+        <svg-icon icon-class="tian" className="tian-bg" v-if="BoxbgType == 0" :style="{ color: '#DEDEDE' }" />
+        <svg-icon icon-class="mi" className="tian-bg" v-if="BoxbgType == 1" :style="{ color: '#DEDEDE' }" />
       </div>
     </div>
   </div>
 </template>
 
 <script>
-import { getContentFile } from "@/api/api";
-const HanziWriter = require("hanzi-writer");
+import { getContentFile } from '@/api/api';
+const HanziWriter = require('hanzi-writer');
 export default {
-  name: "Strockred",
+  name: 'Strockred',
   components: {},
-  props: [
-    "targetDiv",
-    "hanzicolor",
-    "Book_text",
-    "wordNum",
-    "tianColor",
-    "drawingColor",
-    "BoxbgType",
-  ],
+  props: ['targetDiv', 'hanzicolor', 'Book_text', 'wordNum', 'tianColor', 'drawingColor', 'BoxbgType', 'fontSize'],
   data() {
     return {
       writer: null,
-      colorsList: ["#404040", "#f65d4d", "#19b068", "#52a1ea", "#ff8c49"],
+      colorsList: ['#404040', '#f65d4d', '#19b068', '#52a1ea', '#ff8c49'],
     };
   },
   computed: {},
@@ -65,6 +43,18 @@ export default {
       // 深度观察监听
       deep: true,
     },
+    fontSize: {
+      handler: function (val, oldVal) {
+        if (val != oldVal) {
+          let _this = this;
+          _this.$nextTick(() => {
+            _this.initHanziwrite();
+          });
+        }
+      },
+      // 深度观察监听
+      deep: true,
+    },
   },
   //方法集合
   methods: {
@@ -76,7 +66,7 @@ export default {
       }
       let options = {
         charDataLoader: function (char, onComplete) {
-          let MethodName = "hz_resource_manager-GetHZStrokesContent";
+          let MethodName = 'hz_resource_manager-GetHZStrokesContent';
           let data = {
             hz: char,
           };
@@ -90,11 +80,7 @@ export default {
         strokeColor: _this.drawingColor,
         drawingWidth: 6,
       };
-      _this.writer = HanziWriter.default.create(
-        _this.targetDiv,
-        _this.Book_text,
-        options
-      );
+      _this.writer = HanziWriter.default.create(_this.targetDiv, _this.Book_text, options);
       _this.writer.quiz();
     },
     resetHanzi() {
@@ -103,8 +89,8 @@ export default {
     },
     updateColor(color) {
       let _this = this;
-      _this.writer.updateColor("strokeColor", color);
-      _this.writer.updateColor("drawingColor", color);
+      _this.writer.updateColor('strokeColor', color);
+      _this.writer.updateColor('drawingColor', color);
     },
   },
   //生命周期 - 创建完成(可以访问当前this实例)
@@ -125,8 +111,8 @@ export default {
   activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
 };
 </script>
-<style lang='scss' scoped>
-@import "../../assets/Scss/_handle.scss";
+<style lang="scss" scoped>
+@import '../../assets/Scss/_handle.scss';
 
 //@import url(); 引入公共css类
 .strockredBox {
@@ -165,4 +151,4 @@ export default {
     font-size: 28px;
   }
 }
-</style>
+</style>

+ 109 - 0
src/views/wordcard/AudioPlay.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="audio-wrapper">
+    <span :class="[url ? 'audio-play' : 'audio-play not-url']" @click="playAudio">
+      <img
+        :src="
+          audio.paused
+            ? require('../../assets/teacherdev/icon-voice-red.png')
+            : require('../../assets/teacherdev/icon-voice-play-red.png')
+        "
+        class="voice-play"
+      />
+    </span>
+    <audio :id="fileId" :ref="fileId" :src="url" preload="metadata"></audio>
+  </div>
+</template>
+
+<script>
+import { GetFileURLMap } from '@/api/api';
+
+export default {
+  name: 'AudioPlay',
+  props: {
+    fileId: {
+      type: String,
+      required: true,
+    },
+    themeColor: {
+      type: String,
+      default: '',
+    },
+  },
+  data() {
+    return {
+      url: '',
+      audio: {
+        paused: true,
+      },
+    };
+  },
+  computed: {},
+  watch: {
+    fileId: {
+      handler(val) {
+        if (!val) return;
+        GetFileURLMap({ file_id_list: [val] }).then(({ url_map }) => {
+          this.url = url_map[val];
+        });
+      },
+      immediate: true,
+    },
+  },
+  mounted() {
+    if (!this.fileId) return;
+    this.$refs[this.fileId].addEventListener('ended', () => {
+      this.audio.paused = true;
+    });
+    this.$refs[this.fileId].addEventListener('pause', () => {
+      this.audio.paused = true;
+    });
+    this.$refs[this.fileId].addEventListener('play', () => {
+      this.audio.paused = false;
+    });
+  },
+  methods: {
+    playAudio() {
+      if (!this.url) return;
+      const audio = this.$refs[this.fileId];
+      let audioArr = document.getElementsByTagName('audio');
+      if (audioArr && audioArr.length > 0) {
+        for (let i = 0; i < audioArr.length; i++) {
+          if (audioArr[i].src === this.url) {
+            if (audioArr[i].id !== this.fileId) {
+              audioArr[i].pause();
+            }
+          } else {
+            audioArr[i].pause();
+          }
+        }
+      }
+      audio.paused ? audio.play() : audio.pause();
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.audio-wrapper {
+  .audio-play {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 40px;
+    height: 40px;
+    color: #fff;
+    cursor: pointer;
+    border-radius: 50%;
+
+    &.not-url {
+      color: #a1a1a1;
+      cursor: not-allowed;
+    }
+
+    .voice-play {
+      width: 20px;
+      height: 20px;
+    }
+  }
+}
+</style>

+ 123 - 0
src/views/wordcard/UploadDrag.vue

@@ -0,0 +1,123 @@
+<template>
+  <div class="upload-wrapper upload-wrapper-drag">
+    <el-upload
+      ref="upload"
+      accept=".jpg,.png"
+      drag
+      :before-upload="beforeUpload"
+      :action="url"
+      :on-exceed="handleExceed"
+      :limit="limit"
+      :on-success="handleSuccess"
+      :file-list="fileList"
+    >
+      <div class="plus-icon"><i class="el-icon-plus"></i></div>
+      <div class="cn-tips">点击或拖拽图片到此上传</div>
+      <div class="en-tips">Only png, jpg can be uploaded, and the size does not exceed 100MB</div>
+    </el-upload>
+  </div>
+</template>
+
+<script>
+import { getToken } from '@/utils/auth';
+export default {
+  name: 'UploadAudio',
+  props: {
+    limit: {
+      type: Number,
+      default: 1,
+    },
+    itemIndex: {
+      type: Number,
+      default: null,
+    },
+    fileList: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  data() {
+    return {};
+  },
+  computed: {
+    url() {
+      let userInfor = JSON.parse(getToken());
+      let SessionID = '';
+      let UserCode = '';
+      let UserType = '';
+      if (userInfor) {
+        UserCode = userInfor.user_code;
+        UserType = userInfor.user_type;
+        SessionID = userInfor.session_id;
+      }
+      return `${process.env.VUE_APP_BASE_API}/GCLSFileServer/WebFileUpload?UserCode=${UserCode}&UserType=${UserType}&SessionID=${SessionID}&SecurityLevel=Mid"`;
+    },
+  },
+  watch: {},
+  methods: {
+    beforeUpload(file) {
+      // 可以用来限制文件大小
+      if (file.size > 100 * 1024 * 1024) {
+        this.$message.warning('上传图片大小不能超过100M');
+        return false; // 必须返回false
+      }
+    },
+    upload(file) {},
+    handleExceed(files, fileList) {
+      this.$message.warning(
+        `当前限制选择 ${this.limit ? this.limit : 1} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${
+          files.length + fileList.length
+        } 个文件`,
+      );
+    },
+    handleSuccess(response, file, fileList) {
+      if (response.status === 1) {
+        this.$message.success('上传成功');
+        this.$emit('changeFillId', response.file_info_list[0], fileList);
+      }
+    },
+    onProgress(event, file, fileList) {
+      let num = ((event.loaded / event.total) * 100) | 0; //百分比
+      this.curPercentage = num;
+      if (this.curPercentage == 100) {
+        this.isShowJinDuTiao = false;
+        this.curPercentage = 0;
+      }
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.upload-wrapper {
+  min-height: 224px;
+  background-color: #f5f5f5;
+  .plus-icon {
+    margin: 50px auto 20px auto;
+    color: #4e5969;
+  }
+  .cn-tips {
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 22px;
+    color: #1d2129;
+  }
+  .en-tips {
+    font-size: 12px;
+    font-weight: 400;
+    line-height: 20px;
+    color: #86909c;
+  }
+}
+</style>
+<style lang="scss">
+.upload-wrapper-drag {
+  .el-upload {
+    width: 100%;
+  }
+  .el-upload-dragger {
+    border: none;
+    background-color: transparent;
+    width: 100%;
+  }
+}
+</style>

+ 194 - 92
src/views/wordcard/cread.vue

@@ -4,8 +4,8 @@
     <div class="main" v-if="!isPreview">
       <div class="from">
         <div class="type">
-          <div :class="[typeIndex == 0 ? 'sele' : '']" @click="cutType(0)">字模式</div>
-          <div :class="[typeIndex == 1 ? 'sele' : '']" @click="cutType(1)">模式</div>
+          <div :class="[typeIndex == 0 ? 'sele' : '']" @click="cutType(0)">字模式</div>
+          <div :class="[typeIndex == 1 ? 'sele' : '']" @click="cutType(1)">模式</div>
         </div>
         <div class="from_main">
           <div class="left">
@@ -52,40 +52,55 @@
               >
               </el-switch>
               <template v-if="typeIndex == 0">
-                <el-switch active-color="#424242" v-model="from.StorkesUnfold" active-text="笔顺展开"> </el-switch>
+                <el-switch
+                  active-color="#424242"
+                  v-model="from.StorkesUnfold"
+                  active-text="笔顺展开"
+                  style="margin-right: 40px"
+                >
+                </el-switch>
+                <el-switch
+                  active-color="#424242"
+                  v-model="from.wordPinyin"
+                  active-text="拼音"
+                  style="margin-right: 40px"
+                >
+                </el-switch>
+                <el-switch active-color="#424242" v-model="from.wordVoice" active-text="读音"> </el-switch>
               </template>
             </div>
             <div class="title" style="margin-top: 24px">书写配置:</div>
             <div class="dv">
               <div style="margin-right: 16px">
-                <span class="title">书写格</span>
+                <span class="title">描红</span>
                 <el-input
                   style="width: 60px; text-align: center"
-                  v-model="from.writeBoxNumber"
-                  onkeyup="value=value.replace(/^0+(\d)|[^\d]+/g,'')"
+                  v-model="from.miaoRedBoxNumber"
                   class="numbre-input"
+                  onkeyup="value=value.replace(/^0+(\d)|[^\d]+/g,'')"
                 ></el-input>
                 <span style="margin-left: 8px">{{ typeIndex == 0 ? '行' : '句' }}</span>
               </div>
               <div style="margin-right: 16px">
-                <span class="title">描红</span>
+                <span class="title">默写</span>
                 <el-input
                   style="width: 60px; text-align: center"
-                  v-model="from.miaoRedBoxNumber"
-                  class="numbre-input"
+                  v-model="from.writeBoxNumber"
                   onkeyup="value=value.replace(/^0+(\d)|[^\d]+/g,'')"
+                  class="numbre-input"
                 ></el-input>
-                <span style="margin-left: 8px">{{ typeIndex == 0 ? '' : '句' }}</span>
+                <span style="margin-left: 8px">{{ typeIndex == 0 ? '' : '句' }}</span>
               </div>
               <div>
-                <span class="title">文末空行</span>
+                <!-- <span class="title">文末空行</span>
                 <el-input
                   style="width: 60px; text-align: center"
                   v-model="from.lastNullrow"
                   onkeyup="value=value.replace(/^0+(\d)|[^\d]+/g,'')"
                   class="numbre-input"
                 ></el-input>
-                <span style="margin-left: 8px">行</span>
+                <span style="margin-left: 8px">行</span> -->
+                <el-checkbox class="auto-complete" v-model="from.autoCompletion">自动补足页面空白</el-checkbox>
               </div>
             </div>
             <div class="dv">
@@ -113,6 +128,8 @@
             :data="itemT"
             :pageNumber="indexT + 1"
             :totalNumber="writeTableData.result.length"
+            :fontSize="from.fontSize"
+            :audio_file_obj="writeTableData.audio_file_obj"
           />
         </div>
       </div>
@@ -134,6 +151,8 @@
               :data="writeTableData.result[previewIndex]"
               :pageNumber="previewIndex + 1"
               :totalNumber="writeTableData.result.length"
+              :fontSize="from.fontSize"
+              :audio_file_obj="writeTableData.audio_file_obj"
             />
           </div>
         </div>
@@ -159,6 +178,7 @@ import writeTable from './writeTable.vue';
 import html2canvas from 'html2canvas';
 import { jsPDF } from 'jspdf';
 import canvg from 'canvg';
+import { pinyin } from 'pinyin-pro';
 
 import FileSaver from 'file-saver';
 import htmlDocx from 'html-docx-js/dist/html-docx';
@@ -180,9 +200,12 @@ export default {
         fontSize: 'center',
         playStorkes: true,
         StorkesUnfold: true,
+        wordPinyin: true, // 拼音
+        wordVoice: true, // 读音
         writeBoxNumber: '0', //书写行
         miaoRedBoxNumber: '0', //描红格
         lastNullrow: '0', //文本末空行数
+        autoCompletion: true, // 自动补足页面空白
         miaoRedBgcolor: '#E1E1E1', //描红底色
         writeColor: '#000000', //书写颜色
         borderColor: '#D65353', //边框颜色
@@ -275,14 +298,12 @@ export default {
         return false;
       }
       // 开始打印
-      console.log(content);
       var contentWidth = content.width;
       var contentHeight = content.height;
       //以下是对svg的处理
       var nodesToRecover = [];
       var nodesToRemove = [];
       var svgElem = $('#pdfDom').find('svg-icon'); //divReport为需要截取成图片的dom的id
-      console.log(svgElem);
       svgElem.each(function (index, node) {
         var parentNode = node.parentNode;
         var svg = node.outerHTML.trim();
@@ -399,6 +420,8 @@ export default {
         typeIndex: this.typeIndex,
         playStorkes: this.from.playStorkes,
         StorkesUnfold: this.from.StorkesUnfold,
+        wordVoice: this.from.wordVoice,
+        wordPinyin: this.from.wordPinyin,
         BoxbgType: this.from.BoxbgType,
         miaoRedBgcolor: this.from.miaoRedBgcolor,
         writeColor: this.from.writeColor,
@@ -415,6 +438,7 @@ export default {
         data.pageNumber = 9;
         data.marginBottom = '15px';
         data.playWidth = '11px';
+        data.firstPageLine = 5;
       } else if (this.from.fontSize == 'center') {
         data.width = '49px';
         data.fontSize = '38px';
@@ -422,6 +446,7 @@ export default {
         data.pageNumber = 12;
         data.marginBottom = '8px';
         data.playWidth = '9px';
+        data.firstPageLine = 6;
       } else {
         data.width = '41px';
         data.fontSize = '31px';
@@ -429,23 +454,21 @@ export default {
         data.pageNumber = 14;
         data.marginBottom = '7px';
         data.playWidth = '8px';
+        data.firstPageLine = 7;
       }
       if (this.from.content == '') {
         this.$message.warning('请先输入内容');
         return;
       }
-      if (!(this.from.writeBoxNumber || this.from.miaoRedBoxNumber || this.from.lastNullrow)) {
+      if (!(this.from.writeBoxNumber || this.from.miaoRedBoxNumber || this.from.autoCompletion)) {
         this.$message.warning('请先输入配置内容');
         return;
       }
       if (Number(this.from.writeBoxNumber) < Number(this.from.miaoRedBoxNumber) && this.typeIndex == 1) {
-        this.$message.warning('书写格数不能小于描红数');
+        this.$message.warning('默写数不能小于描红数');
         return;
-      } else if (
-        Number(this.from.writeBoxNumber) < Math.ceil(this.from.miaoRedBoxNumber / data.rowNumber) &&
-        this.typeIndex == 0
-      ) {
-        this.$message.warning('书写格数不能小于描红所用行数');
+      } else if (Number(this.from.writeBoxNumber) < Math.ceil(this.from.miaoRedBoxNumber) && this.typeIndex == 0) {
+        this.$message.warning('默写数不能小于描红所用行数');
         return;
       }
       let option = [];
@@ -513,10 +536,12 @@ export default {
         });
     },
     // 生成
-    handleEvent(hzDetailList, oldData) {
+    async handleEvent(hzDetailList, oldData) {
       this.writeTableData = null;
       let data = JSON.parse(JSON.stringify(oldData));
       let contentArr = this.from.content.split('\n');
+      let final_result = [];
+      let audio_file_obj = {};
       for (let i = 0; i < contentArr.length; i++) {
         if (data.typeIndex == 0) {
           if (contentArr[i].length == 1) {
@@ -527,14 +552,22 @@ export default {
               data.option.push(obj);
             }
           } else {
+            let conItem = '';
             for (let m = 0; m < contentArr[i].length; m++) {
               if (/^[\u4e00-\u9fa5]/.test(contentArr[i][m])) {
-                let obj = {
-                  con: contentArr[i][m],
-                };
-                data.option.push(obj);
+                conItem += contentArr[i][m];
+                // let obj = {
+                //   con: contentArr[i][m],
+                // };
+                // data.option.push(obj);
               }
             }
+            if (conItem) {
+              let obj = {
+                con: conItem,
+              };
+              data.option.push(obj);
+            }
           }
         } else {
           let contentItem = '';
@@ -546,15 +579,33 @@ export default {
           if (contentItem) data.option.push(contentItem);
         }
       }
-      //   字模式 笔顺打开
-      if (data.typeIndex == 0) {
-        data.option.forEach((item, i) => {
-          item.hzDetail = {
-            hz_json: null,
+      await data.option.forEach(async (item, i) => {
+        let hz_list = [];
+        item.con.split('').forEach((items) => {
+          let res = JSON.parse(JSON.stringify(hzDetailList[items]));
+          let obj = {
+            con: items,
+            hzDetail: {
+              hz_json: res,
+            },
           };
-          let res = JSON.parse(JSON.stringify(hzDetailList[item.con]));
-          this.$set(data.option[i].hzDetail, 'hz_json', res);
+          hz_list.push(obj);
+        });
+        item.hz_list = hz_list;
+        item.pinyin = pinyin(item.con);
+        let MethodName = 'tool-PinyinToVoiceFile';
+        let datas = {
+          pinyin: item.pinyin.split(' ').join(','),
+        };
+        await getLogin(MethodName, datas).then((res) => {
+          if (res.status === 1) {
+            audio_file_obj[item.con] = res.file_id;
+          }
         });
+      });
+
+      //   字模式 笔顺打开
+      if (data.typeIndex == 0) {
         if (data.StorkesUnfold) {
           let allArr = [];
           data.option.forEach((item) => {
@@ -562,93 +613,130 @@ export default {
             let hzLength = 1;
             let arrOption = [];
             // 拆分字和笔画为每一项
-            arr.push({
-              con: item.con,
-              hzDetail: JSON.parse(JSON.stringify(item.hzDetail.hz_json)),
-            });
-            if (item.hzDetail && item.hzDetail.hz_json && item.hzDetail.hz_json.medians) {
-              hzLength += item.hzDetail.hz_json.medians.length;
-              item.hzDetail.hz_json.medians.forEach((items, indexs) => {
-                arr.push({
-                  con: item.con,
-                  answer: indexs + 1,
-                  hzDetail: JSON.parse(JSON.stringify(item.hzDetail.hz_json)),
+            item.hz_list.forEach((items, indexss) => {
+              let item_row = []; // 每个字的描红内容
+              if (items.hzDetail && items.hzDetail.hz_json && items.hzDetail.hz_json.medians) {
+                items.hzDetail.hz_json.medians.forEach((itemss, indexs) => {
+                  item_row.push({
+                    con: items.con,
+                    answer: indexs + 1,
+                    hzDetail: JSON.parse(JSON.stringify(items.hzDetail.hz_json)),
+                  });
                 });
-              });
-            }
-            // 如果不满一行则补满
-            let newarr = [];
-            if (arr.length % data.rowNumber != 0) {
-              let num = data.rowNumber - (arr.length % data.rowNumber);
-              for (let i = 0; i < num; i++) {
-                arr.push({});
               }
-              if (arr.length > data.rowNumber) {
-                newarr = this.arrSplice(arr, data.rowNumber);
-                newarr.forEach((itemss) => {
-                  allArr.push(itemss);
-                });
+              if (item_row.length % data.rowNumber !== 0) {
+                let num = data.rowNumber - (item_row.length % data.rowNumber);
+                for (let i = 0; i < num; i++) {
+                  item_row.push({});
+                }
+                if (item_row.length > data.rowNumber) {
+                  newarr = this.arrSplice(item_row, data.rowNumber);
+                  newarr.forEach((itemss) => {
+                    arr.push(itemss);
+                  });
+                } else {
+                  arr.push(item_row);
+                }
               } else {
-                allArr.push(arr);
+                arr.push(item_row);
               }
-            } else {
-              allArr.push(arr);
-            }
+            });
             // 添加书写行
             if (data.writeBoxNumber) {
               for (let i = 0; i < data.writeBoxNumber; i++) {
                 let numrow = [];
                 for (let k = 0; k < data.rowNumber; k++) {
                   numrow.push({
-                    con: item.con,
+                    con: item.con.split('')[0],
                     write: true,
                   });
                 }
                 // 描红格
                 if (i == 0 && data.miaoRedBoxNumber) {
                   let m =
-                    Math.ceil(data.miaoRedBoxNumber / data.rowNumber) > data.writeBoxNumber
-                      ? data.writeBoxNumber
-                      : Math.ceil(data.miaoRedBoxNumber / data.rowNumber);
-                  for (let j = 0; j < m; j++) {
-                    let miaoArr = [];
-                    for (let l = 0; l < data.rowNumber; l++) {
-                      if (j * data.rowNumber + l < data.miaoRedBoxNumber) {
-                        miaoArr[l] = {
-                          con: item.con,
+                    Number(data.miaoRedBoxNumber) > Number(data.writeBoxNumber)
+                      ? Number(data.writeBoxNumber)
+                      : Number(data.miaoRedBoxNumber);
+                  let s = Math.ceil((m * data.rowNumber) / item.hz_list.length); // 循环多少次词组
+                  let miaoArr = [];
+                  for (let j = 0; j < s; j++) {
+                    item.hz_list.forEach((items) => {
+                      if (miaoArr.length < m * data.rowNumber) {
+                        miaoArr.push({
+                          con: items.con,
                           miaoRed: true,
                           write: true,
-                        };
-                      } else {
-                        miaoArr[l] = {
-                          con: item.con,
-                          write: true,
-                        };
+                        });
                       }
-                    }
-                    arrOption.push(JSON.parse(JSON.stringify(miaoArr)));
+                    });
+                  }
+                  let result = []; // 将描红数组切割为二维数组
+                  for (let i = 0; i < miaoArr.length; i += data.rowNumber) {
+                    result.push(miaoArr.slice(i, i + data.rowNumber));
                   }
+                  arrOption = JSON.parse(JSON.stringify(result));
                 }
                 arrOption.push(numrow);
                 arrOption = arrOption.slice(0, data.writeBoxNumber);
               }
               arrOption.forEach((itemA) => {
-                allArr.push(itemA);
+                arr.push(itemA);
               });
             }
-          });
-          if (data.lastNullrow) {
-            for (let i = 0; i < data.lastNullrow; i++) {
-              let numrow = [];
-              for (let k = 0; k < data.rowNumber; k++) {
-                numrow.push({
-                  write: true,
+            if (this.from.autoCompletion) {
+              let complet_line = 0; // 需要补齐的行数
+              if (arr.length <= data.firstPageLine) {
+                complet_line = data.firstPageLine - arr.length;
+              } else {
+                complet_line = data.pageNumber - ((arr.length - data.firstPageLine) % data.pageNumber);
+              }
+              for (let i = 0; i < complet_line; i++) {
+                let numrow = [];
+                for (let k = 0; k < data.rowNumber; k++) {
+                  numrow.push({
+                    write: true,
+                  });
+                }
+                arr.push(numrow);
+              }
+            }
+            if (arr.length <= data.firstPageLine) {
+              let obj = {
+                fileList: [],
+                info: {
+                  definition: '',
+                  collocation: '',
+                  exampleSent: '',
+                },
+                list: arr,
+                hz_info: item.hz_list,
+                pinyin: item.pinyin,
+                con: item.con,
+              };
+              final_result.push(obj);
+            } else {
+              let obj = {
+                fileList: [],
+                info: {
+                  definition: '',
+                  collocation: '',
+                  exampleSent: '',
+                },
+                list: arr.slice(0, data.firstPageLine),
+                hz_info: item.hz_list,
+                pinyin: item.pinyin,
+                con: item.con,
+              };
+              final_result.push(obj);
+              for (let i = data.firstPageLine; i < arr.length; i += data.pageNumber) {
+                final_result.push({
+                  list: arr.slice(i, i + data.pageNumber),
                 });
               }
-              allArr.push(numrow);
             }
-          }
-          data.result = this.arrSplice(allArr, data.pageNumber);
+          });
+          data.result = final_result;
+          data.audio_file_obj = audio_file_obj;
           this.writeTableData = data;
         } else {
           let allArr = [];
@@ -745,6 +833,7 @@ export default {
           data.result = this.arrSplice(allArr, data.pageNumber);
           this.writeTableData = data;
         }
+        console.log(this.writeTableData);
         this.$forceUpdate();
         this.isCread = true;
       } else {
@@ -832,6 +921,7 @@ export default {
           });
         });
         this.writeTableData = data;
+        console.log(this.writeTableData);
         this.$forceUpdate();
         this.isCread = true;
       }
@@ -915,9 +1005,12 @@ export default {
         fontSize: 'center',
         playStorkes: true,
         StorkesUnfold: true,
+        wordPinyin: true, // 拼音
+        wordVoice: true, // 读音
         writeBoxNumber: '0', //书写行
         miaoRedBoxNumber: '0', //描红格
         lastNullrow: '0', //文本末空行数
+        autoCompletion: true, // 自动补足页面空白
         miaoRedBgcolor: '#E1E1E1', //描红底色
         writeColor: '#000000', //书写颜色
         borderColor: '#D65353', //边框颜色
@@ -958,19 +1051,19 @@ export default {
       box-sizing: border-box;
       .type {
         display: flex;
-        width: 104px;
+        width: 116px;
         height: 28px;
         background: #eeeeee;
         border-radius: 4px;
         padding: 2px;
         > div {
-          width: 52px;
           font-weight: 400;
           font-size: 12px;
           line-height: 28px;
           text-align: center;
           color: #888888;
           cursor: pointer;
+          padding: 0 8px;
         }
         .sele {
           font-weight: 500;
@@ -1185,5 +1278,14 @@ export default {
       }
     }
   }
+  .auto-complete {
+    .el-checkbox__label {
+      color: #000000;
+    }
+    .el-checkbox__input.is-checked .el-checkbox__inner {
+      background-color: #165dff;
+      border-color: #165dff;
+    }
+  }
 }
 </style>

+ 290 - 93
src/views/wordcard/writeTable.vue

@@ -1,9 +1,101 @@
 <template>
   <div :class="['writeTable']" v-if="data">
-    <div class="writeTop">
+    <div class="writeTop" :class="[data.fileList ? 'writeTop-nopadding' : '']">
+      <template v-if="data.fileList">
+        <UploadDrag
+          :fileList="data.fileList"
+          @changeFillId="changeFillId"
+          v-if="data.fileList.length === 0"
+        ></UploadDrag>
+        <div class="item-image" v-else>
+          <el-image
+            style="width: 593px; height: 224px"
+            :src="data.fileList[0].fileUrl"
+            :preview-src-list="[data.fileList[0].fileUrl]"
+            fit="cover"
+          />
+          <span class="item-image-del" @click="handleDeleteImg"><i class="el-icon-delete"></i></span>
+        </div>
+        <div class="item-info">
+          <template v-if="data.hz_info.length === 1">
+            <div class="item-info-left">
+              <p v-if="data.pinyin || dataConfig.wordVoice" class="voice-box">
+                <AudioPlay :file-id="audio_file" v-if="audio_file" />
+                <span v-if="data.pinyin">{{ data.pinyin }}</span>
+              </p>
+              <div class="hz-box">
+                <Strockplay
+                  className="adult-strockplay"
+                  :strokePlayColor="dataConfig.borderColor"
+                  :Book_text="itemh.con"
+                  :playStorkes="dataConfig.playStorkes"
+                  :strokeColor="dataConfig.fontColor"
+                  :palyWidth="dataConfig.playWidth"
+                  :BoxbgType="dataConfig.BoxbgType"
+                  :curItem="itemh.hzDetail.hz_json"
+                  :targetDiv="'writeTops-item-' + pageNumber + '-' + indexh + '-' + itemh.con"
+                  :fontSize="fontSize"
+                  :class="[indexh !== 0 ? 'writeTop-item-noLeft' : '']"
+                  class="writeTop-item"
+                  v-for="(itemh, indexh) in data.hz_info"
+                  :key="indexh"
+                />
+              </div>
+            </div>
+            <div class="item-info-right">
+              <p class="voice-box" v-if="data.pinyin || data.wordVoice"></p>
+
+              <div class="item-info-row">
+                <div class="item-info-half">
+                  <label>偏旁:</label>
+                  <div :id="'character-target-info-div' + pageNumber"></div>
+                </div>
+                <div class="item-info-half">
+                  <label>笔画:</label>
+                  {{ data.hz_info[0].hzDetail.hz_json.medians.length }}
+                </div>
+              </div>
+              <div class="item-info-row">
+                <el-input v-model="data.info.definition" placeholder="输入释义"></el-input>
+                <el-input v-model="data.info.collocation" placeholder="输入搭配"></el-input>
+              </div>
+              <div class="item-info-row">
+                <el-input v-model="data.info.exampleSent" placeholder="输入例句"></el-input>
+              </div>
+            </div>
+          </template>
+          <template v-else>
+            <div class="item-info-left item-info-left-long">
+              <p v-if="dataConfig.pinyin || dataConfig.wordVoice" class="voice-box">
+                <AudioPlay :file-id="audio_file" v-if="audio_file" />
+                <span v-if="data.pinyin">{{ data.pinyin }}</span>
+              </p>
+              <div class="hz-box">
+                <Strockplay
+                  className="adult-strockplay"
+                  :strokePlayColor="dataConfig.borderColor"
+                  :Book_text="itemh.con"
+                  :playStorkes="dataConfig.playStorkes"
+                  :strokeColor="dataConfig.fontColor"
+                  :palyWidth="dataConfig.playWidth"
+                  :BoxbgType="dataConfig.BoxbgType"
+                  :curItem="itemh.hzDetail.hz_json"
+                  :targetDiv="'writeTops-item-' + pageNumber + '-' + indexh + '-' + itemh.con"
+                  :fontSize="fontSize"
+                  :class="[indexh !== 0 ? 'writeTop-item-noLeft' : '']"
+                  class="writeTop-item writeTop-item-small"
+                  v-for="(itemh, indexh) in data.hz_info"
+                  :key="indexh"
+                />
+              </div>
+              <el-input type="textarea" v-model="data.info.definition" rows="2" placeholder="输入"></el-input>
+            </div>
+          </template>
+        </div>
+      </template>
       <div
         class="writeTop-row"
-        v-for="(itemR, indexR) in data"
+        v-for="(itemR, indexR) in data.list"
         :key="indexR"
         :style="{ marginBottom: dataConfig.marginBottom }"
       >
@@ -18,25 +110,9 @@
             borderColor: dataConfig.borderColor,
           }"
         >
-          <template
-            v-if="
-              itemI &&
-              itemI.con &&
-              !itemI.write &&
-              !itemI.answer &&
-              !itemI.miaoRed
-            "
-          >
+          <template v-if="itemI && itemI.con && !itemI.write && !itemI.answer && !itemI.miaoRed">
             <Strockplay
-              v-if="
-                'writeTop-item-' +
-                pageNumber +
-                '-' +
-                indexR +
-                '-' +
-                indexI +
-                type
-              "
+              v-if="'writeTop-item-' + pageNumber + '-' + indexR + '-' + indexI + type + fontSize"
               className="adult-strockplay"
               :strokePlayColor="dataConfig.borderColor"
               :Book_text="itemI.con"
@@ -45,16 +121,8 @@
               :palyWidth="dataConfig.playWidth"
               :BoxbgType="dataConfig.BoxbgType"
               :curItem="itemI.hzDetail"
-              :targetDiv="
-                'writeTop-item-' +
-                pageNumber +
-                '-' +
-                indexR +
-                '-' +
-                indexI +
-                type +
-                itemI.con
-              "
+              :targetDiv="'writeTop-item-' + pageNumber + '-' + indexR + '-' + indexI + type + itemI.con"
+              :fontSize="fontSize"
             />
           </template>
           <template v-else-if="itemI && itemI.answer">
@@ -66,33 +134,14 @@
               :strokeNumber="itemI.answer"
               :curItem="itemI.hzDetail"
               :BoxbgType="dataConfig.BoxbgType"
-              :targetDiv="
-                'write-item-miaohong-' +
-                pageNumber +
-                '-' +
-                indexR +
-                '-' +
-                indexI +
-                type +
-                itemI.con
-              "
-              :targetDivGray="
-                'write-item-miaohong-gray-' +
-                pageNumber +
-                '-' +
-                indexR +
-                '-' +
-                indexI +
-                type +
-                itemI.con
-              "
+              :targetDiv="'write-item-miaohong-' + pageNumber + '-' + indexR + '-' + indexI + type + itemI.con"
+              :targetDivGray="'write-item-miaohong-gray-' + pageNumber + '-' + indexR + '-' + indexI + type + itemI.con"
+              :fontSize="fontSize"
             />
           </template>
           <template v-else-if="itemI && itemI.miaoRed">
             <Strockred
-              v-if="
-                'write-item-' + pageNumber + '-' + indexR + '-' + indexI + type
-              "
+              v-if="'write-item-' + pageNumber + '-' + indexR + '-' + indexI + type"
               className="adult-strockplay"
               :strokePlayColor="dataConfig.borderColor"
               :Book_text="itemI.con"
@@ -100,47 +149,26 @@
               :hanzicolor="dataConfig.miaoRedBgcolor"
               :drawingColor="dataConfig.writeColor"
               :BoxbgType="dataConfig.BoxbgType"
-              :targetDiv="
-                'write-item-' +
-                pageNumber +
-                '-' +
-                indexR +
-                '-' +
-                indexI +
-                type +
-                itemI.con
-              "
+              :targetDiv="'write-item-' + pageNumber + '-' + indexR + '-' + indexI + type + itemI.con"
+              :fontSize="fontSize"
             />
           </template>
-          <div
-            v-else-if="itemI"
-            class="tian-div"
-            @click="freeWrite(itemI, indexR, indexI)"
-          >
+          <div v-else-if="itemI" class="tian-div" @click="freeWrite(itemI, indexR, indexI)">
             <svg-icon
               icon-class="tian"
               className="tian"
               v-if="dataConfig.BoxbgType == 0"
               :style="{ color: '#DEDEDE' }"
             />
-            <svg-icon
-              icon-class="mi"
-              className="tian"
-              v-if="dataConfig.BoxbgType == 1"
-              :style="{ color: '#DEDEDE' }"
-            />
-            <img
-              v-if="itemI && itemI.strokes_image_url"
-              :src="itemI.strokes_image_url"
-              alt=""
-            />
+            <svg-icon icon-class="mi" className="tian" v-if="dataConfig.BoxbgType == 1" :style="{ color: '#DEDEDE' }" />
+            <img v-if="itemI && itemI.strokes_image_url" :src="itemI.strokes_image_url" alt="" />
           </div>
         </div>
       </div>
     </div>
     <div class="writeBottom">
       <span>BLCUP 全球国际中文教学云平台</span>
-      <b>{{ pageNumber + "/" + totalNumber }}</b>
+      <b>{{ pageNumber + '/' + totalNumber }}</b>
       <a>www.chinesedu.com</a>
     </div>
     <div class="practiceBox practiceBoxStrock" v-if="ifFreeShow">
@@ -161,10 +189,14 @@
 
 <script>
 //这里可以导入其它文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
-import StrockplayredlineTable from "../../components/corpus/StrockplayredlineTable.vue";
-import Strockplay from "../../components/corpus/Strockplay.vue";
-import Strockred from "../../components/corpus/Strockred.vue";
-import FreewriteLettle from "../../components/corpus/FreewriteLettle.vue";
+import StrockplayredlineTable from '../../components/corpus/StrockplayredlineTable.vue';
+import Strockplay from '../../components/corpus/Strockplay.vue';
+import Strockred from '../../components/corpus/Strockred.vue';
+import FreewriteLettle from '../../components/corpus/FreewriteLettle.vue';
+import UploadDrag from './UploadDrag.vue';
+import AudioPlay from './AudioPlay.vue';
+const HanziWriter = require('hanzi-writer');
+import { getLogin } from '@/api/api';
 
 export default {
   //import引入的组件需要注入到对象中才能使用
@@ -173,14 +205,24 @@ export default {
     Strockplay,
     Strockred,
     FreewriteLettle,
+    UploadDrag,
+    AudioPlay,
   },
-  props: ["dataConfig", "data", "pageNumber", "totalNumber", "type", "none"],
+  props: ['dataConfig', 'data', 'pageNumber', 'totalNumber', 'type', 'none', 'fontSize', 'audio_file_obj'],
   data() {
     //这里存放数据
     return {
       ifFreeShow: false,
       activeIndex: null,
       activeColIndex: null,
+      fileList: [],
+      info: {
+        definition: '',
+        collocation: '',
+        exampleSent: '',
+      },
+      writer: null,
+      audio_file: '',
     };
   },
   //计算属性 类似于data概念
@@ -209,17 +251,65 @@ export default {
     },
     ExerciseChangeCurQue(answer, rowIndex, colIndex) {
       if (answer) {
-        this.data[rowIndex][colIndex].strokes_image_url =
-          answer.strokes_image_url;
-        this.data[rowIndex][colIndex].history = answer.history;
+        this.data.list[rowIndex][colIndex].strokes_image_url = answer.strokes_image_url;
+        this.data.list[rowIndex][colIndex].history = answer.history;
         this.$forceUpdate();
       }
     },
+    changeFillId(file, fileList) {
+      let obj = {
+        name: file.name,
+        fileId: file.file_id,
+        fileUrl: file.file_url,
+      };
+      this.data.fileList.push(obj);
+    },
+    handleDeleteImg() {
+      this.data.fileList = [];
+    },
+    initHanziwrite() {
+      let _this = this;
+      let node = document.getElementById(`character-target-info-div` + _this.pageNumber);
+      if (node && node.children.length > 1) {
+        node.removeChild(node.children[1]);
+      }
+      //var ren = require("hanzi-writer-data/国");
+      _this.writer = HanziWriter.default.create(
+        `character-target-info-div` + _this.pageNumber,
+        _this.data.hz_info[0].con,
+        {
+          width: 22,
+          height: 22,
+          padding: 0,
+          radicalColor: '#000000',
+          strokeColor: '#fff',
+        },
+      );
+    },
   },
   //生命周期 - 创建完成(可以访问当前this实例)
-  created() {},
+  created() {
+    if (this.data.pinyin) {
+      let MethodName = 'tool-PinyinToVoiceFile';
+      let datas = {
+        pinyin: this.data.pinyin.split(' ').join(','),
+      };
+      getLogin(MethodName, datas).then((res) => {
+        if (res.status === 1) {
+          this.audio_file = res.file_id;
+        }
+      });
+    }
+  },
   //生命周期 - 挂载完成(可以访问DOM元素)
-  mounted() {},
+  mounted() {
+    let _this = this;
+    _this.$nextTick(() => {
+      if (_this.data.hz_info && _this.data.hz_info.length === 1) {
+        _this.initHanziwrite();
+      }
+    });
+  },
   //生命周期-创建之前
   beforeCreated() {},
   //生命周期-挂载之前
@@ -247,16 +337,123 @@ export default {
   .writeTop {
     height: 732px;
     padding-top: 48px;
+    .item-image {
+      position: relative;
+      .item-image-del {
+        position: absolute;
+        top: 8px;
+        right: 8px;
+        width: 16px;
+        height: 16px;
+        display: block;
+        cursor: pointer;
+        background-color: #ffffff;
+        color: #ee3232;
+        padding: 8px;
+        border-radius: 50%;
+        box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
+      }
+    }
     .writeTop-row {
       display: flex;
       justify-content: center;
+    }
+  }
+  .writeTop-nopadding {
+    padding-top: 0;
+    height: 780px;
+  }
+  .item-info {
+    display: flex;
+    width: 100%;
+    padding: 16px 46px;
+    column-gap: 16px;
+    box-sizing: border-box;
+    &-left {
       .writeTop-item {
-        border: 1px solid #de4444;
-        &.writeTop-item-noLeft {
-          border-left: none;
+        width: 98px;
+        height: 98px;
+        :deep .strock-play-box {
+          width: 18px !important;
+          height: 18px !important;
+        }
+        :deep .playStorkes-btn {
+          width: 18px !important;
+          height: 18px !important;
         }
+        &-small {
+          width: 62px;
+          height: 62px;
+          :deep .strock-play-box {
+            width: 11px !important;
+            height: 11px !important;
+          }
+          :deep .playStorkes-btn {
+            width: 11px !important;
+            height: 11px !important;
+          }
+        }
+      }
+      &-long {
+        width: 100%;
+      }
+    }
+    &-right {
+      flex: 1;
+    }
+    :deep .el-textarea__inner {
+      resize: none;
+      background-color: #f3f3f3;
+      border: none;
+      outline: none;
+    }
+    .voice-box {
+      width: 100%;
+      height: 32px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      column-gap: 4px;
+      img {
+        width: 24px;
+        height: 24px;
+      }
+      span {
+        font-family: League;
+        font-size: 16px;
+        font-weight: 400;
+        color: #de4444;
+      }
+    }
+    .item-info-row {
+      display: flex;
+      column-gap: 11px;
+      margin-bottom: 6px;
+      :deep .el-input__inner {
+        background-color: #f3f3f3;
+        border: none;
+        outline: none;
+        height: 32px;
       }
     }
+    .item-info-half {
+      width: 50%;
+      display: flex;
+      font-size: 14px;
+      line-height: 22px;
+      height: 22px;
+    }
+  }
+  .hz-box {
+    display: flex;
+    width: max-content;
+    margin: 8px auto;
+  }
+  .writeTop-item {
+    border: 1px solid #de4444;
+  }
+  .writeTop-item-noLeft {
+    border-left: none;
   }
   .writeBottom {
     display: flex;