Просмотр исходного кода

Merge branch 'master' of http://gcls-git.helxsoft.cn/GCLS/eep_page

zq 7 месяцев назад
Родитель
Сommit
5caa57f423
45 измененных файлов с 980 добавлено и 1049 удалено
  1. 1 1
      src/components/CommonPreview.vue
  2. 4 0
      src/icons/svg/play-stroke-icon.svg
  3. 12 9
      src/utils/common.js
  4. 5 5
      src/utils/index.js
  5. 1 1
      src/utils/transform.js
  6. 11 2
      src/utils/validate.js
  7. 13 2
      src/views/book/courseware/create/components/base/common/UploadFile.vue
  8. 0 2
      src/views/book/courseware/create/components/base/h5_games/H5Games.vue
  9. 0 2
      src/views/book/courseware/create/components/base/upload_preview/UploadPreview.vue
  10. 3 1
      src/views/book/courseware/create/components/common/ModuleMixin.js
  11. 0 2
      src/views/book/courseware/create/components/question/character_structure/CharacterStructure.vue
  12. 0 2
      src/views/book/courseware/create/components/question/drawing/Drawing.vue
  13. 27 1
      src/views/book/courseware/create/components/question/fill/Fill.vue
  14. 0 2
      src/views/book/courseware/create/components/question/image_text/ImageText.vue
  15. 257 26
      src/views/book/courseware/create/components/question/newWord_template/NewWordTemplate.vue
  16. 31 1
      src/views/book/courseware/create/components/question/newWord_template/NewWordTemplateSetting.vue
  17. 0 2
      src/views/book/courseware/create/components/question/video_interaction/VideoInteraction.vue
  18. 7 2
      src/views/book/courseware/create/components/question/voice_matrix/CheckSubtitles.vue
  19. 0 1
      src/views/book/courseware/create/components/question/write/Write.vue
  20. 8 8
      src/views/book/courseware/data/bookType.js
  21. 3 0
      src/views/book/courseware/data/fill.js
  22. 0 66
      src/views/book/courseware/data/newWordTemplate copy.js
  23. 51 3
      src/views/book/courseware/data/newWordTemplate.js
  24. 5 1
      src/views/book/courseware/data/voiceMatrix.js
  25. 1 2
      src/views/book/courseware/data/write.js
  26. 2 0
      src/views/book/courseware/preview/common/SoundRecord.vue
  27. 134 24
      src/views/book/courseware/preview/components/fill/FillPreview.vue
  28. 83 0
      src/views/book/courseware/preview/components/fill/components/WriteDialog.vue
  29. 64 855
      src/views/book/courseware/preview/components/newWord_template/NewWordTemplatePreview.vue
  30. 232 0
      src/views/book/courseware/preview/components/newWord_template/components/Strockplayredline.vue
  31. 1 1
      src/views/book/courseware/preview/components/new_word/components/Strockplayredline.vue
  32. 0 1
      src/views/book/courseware/preview/components/record_input/RecordInputPreview.vue
  33. 5 5
      src/views/book/courseware/preview/components/write_base/WriteBasePreview.vue
  34. 1 1
      src/views/personal_workbench/check_task/audit/index.vue
  35. 1 1
      src/views/personal_workbench/common/MenuPopover.vue
  36. 1 1
      src/views/personal_workbench/edit_task/preview/index.vue
  37. 8 8
      src/views/personal_workbench/project/components/MenuTree.vue
  38. 1 1
      src/views/project_manage/book/BookPreview.vue
  39. 1 1
      src/views/project_manage/book/index.vue
  40. 1 1
      src/views/project_manage/org/book/OrgBookPreview.vue
  41. 1 1
      src/views/project_manage/org/book/index.vue
  42. 1 1
      src/views/project_manage/org/final/OrgFinalPreview.vue
  43. 1 1
      src/views/project_manage/org/project/index.vue
  44. 1 1
      src/views/project_manage/project/ProjectPreview.vue
  45. 1 1
      src/views/project_manage/project/index.vue

+ 1 - 1
src/components/CommonPreview.vue

@@ -147,7 +147,7 @@
 import CoursewarePreview from '@/views/book/courseware/preview/CoursewarePreview.vue';
 import MenuPopover from '@/views/personal_workbench/common/MenuPopover.vue';
 import RichText from '@/components/RichText.vue';
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 import MindMap from '@/components/MindMap.vue';
 import VideoPlay from '@/views/book/courseware/preview/components/common/VideoPlay.vue';
 import AudioPlay from '@/views/book/courseware/preview/components/common/AudioPlay.vue';

+ 4 - 0
src/icons/svg/play-stroke-icon.svg

@@ -0,0 +1,4 @@
+<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0 0H22V22C9.99865 18.0776 5.33153 13.0958 0 0Z" fill="currentColor"/>
+<path d="M11.9911 11C12.1384 11 12.2682 10.9487 12.4295 10.8563L16.5721 8.51995C16.8737 8.34892 17 8.21551 17 8C17 7.78449 16.8737 7.6545 16.5721 7.48005L12.4295 5.14367C12.2682 5.05131 12.1384 5 11.9911 5C11.7034 5 11.5 5.21551 11.5 5.55416V10.4458C11.5 10.7879 11.7034 11 11.9911 11Z" fill="white" fill-opacity="0.85"/>
+</svg>

+ 12 - 9
src/utils/common.js

@@ -86,15 +86,6 @@ export function handleToneValue(valItem) {
   return numList.length === 0 ? [{ con: valItem }] : numList;
 }
 
-/**
- * @description 是否为 true
- * @param {'true'|'false'} value
- * @returns {boolean}
- */
-export function isTrue(value) {
-  return value === 'true';
-}
-
 // 全屏方法兼容
 export function fullScreenCompatibility(dom) {
   if (dom.requestFullscreen) {
@@ -133,3 +124,15 @@ export function toggleFullScreen(dom) {
     exitFullScreenCompatibility();
   }
 }
+
+/**
+ * @description 计算数组的深度
+ * @param {Array} arr 需要计算深度的数组
+ * @returns {Number} 数组的深度
+ */
+export function getArrayDepth(arr) {
+  if (!Array.isArray(arr)) return 0;
+  if (arr.length === 0) return 1;
+
+  return 1 + Math.max(...arr.map((item) => (Array.isArray(item) ? getArrayDepth(item) : 0)));
+}

+ 5 - 5
src/utils/index.js

@@ -1,8 +1,8 @@
 /**
  * 生成指定位随机数的函数
- * @param {number} length 随机数的位数
- * @param {boolean} isUpperCase 是否大写
- * @returns {string} 随机36进制数
+ * @param {Number} length 随机数的位数
+ * @param {Boolean} isUpperCase 是否大写
+ * @returns {String} 随机36进制数
  */
 export function getRandomNumber(length = 8, isUpperCase = false) {
   let randomNumber = Math.random()
@@ -16,8 +16,8 @@ export function getRandomNumber(length = 8, isUpperCase = false) {
 
 /**
  * 下载文件
- * @param {string} url 文件地址
- * @param {string} downloadName 文件名称
+ * @param {String} url 文件地址
+ * @param {String} downloadName 文件名称
  */
 export function downloadFile(url, downloadName) {
   const a = document.createElement('a');

+ 1 - 1
src/utils/transform.js

@@ -7,7 +7,7 @@ export function zeroFill(val) {
  * 将秒转为时:分:秒格式
  * @param {Number|String} val 秒
  * @param {'normal'|'chinese'} type 格式类型
- * @returns {string} 'normal' hh:MM:ss 小于1小时返回 MM:ss 'chinese' h时m分s秒
+ * @returns {String} 'normal' hh:MM:ss 小于1小时返回 MM:ss 'chinese' h时m分s秒
  */
 export function secondFormatConversion(val = 0, type = 'normal') {
   const seconds = parseInt(val); // 输入的秒数

+ 11 - 2
src/utils/validate.js

@@ -8,9 +8,18 @@ export function isExternal(path) {
 }
 
 /**
+ * @description 是否为 true
+ * @param {'true'|'false'} value
+ * @returns {Boolean}
+ */
+export function isTrue(value) {
+  return value === 'true';
+}
+
+/**
  * @description 只允许输入两位小数
- * @param {string} value
- * @returns {number}
+ * @param {String} value
+ * @returns {Number}
  */
 export function twoDecimal(value) {
   if (!value) {

+ 13 - 2
src/views/book/courseware/create/components/base/common/UploadFile.vue

@@ -166,6 +166,16 @@ export default {
       type: Object,
       default: () => ({}),
     },
+    index: {
+      // 如果是二维数组里循环上传 一维索引
+      type: Number,
+      default: null,
+    },
+    indexs: {
+      // 如果是二维数组里循环上传 二维索引
+      type: Number,
+      default: null,
+    },
   },
   data() {
     return {
@@ -201,7 +211,7 @@ export default {
   watch: {
     content: {
       handler(val) {
-        this.$emit('updateFileList', val);
+        this.$emit('updateFileList', val, this.index, this.indexs);
       },
       deep: true,
     },
@@ -284,7 +294,8 @@ export default {
         this.type === 'picture' ||
         this.type === 'image_text' ||
         this.type === 'drawing' ||
-        this.type === 'character_structure'
+        this.type === 'character_structure' ||
+        this.type === 'newWord_template'
       ) {
         fileType = ['jpg', 'png', 'jpeg'];
         typeTip = '图片文件只能是 jpg、png、jpeg 格式!';

+ 0 - 2
src/views/book/courseware/create/components/base/h5_games/H5Games.vue

@@ -3,8 +3,6 @@
     <template #content>
       <UploadFile
         key="upload_games"
-        :courseware-id="courseware_id"
-        :component-id="id"
         :type="data.type"
         :total-size="200"
         :file-list="data.file_list"

+ 0 - 2
src/views/book/courseware/create/components/base/upload_preview/UploadPreview.vue

@@ -2,8 +2,6 @@
   <ModuleBase :type="data.type">
     <template #content>
       <UploadFile
-        :courseware-id="courseware_id"
-        :component-id="id"
         :type="data.type"
         :single-size="data.single_size"
         :total-size="data.total_size"

+ 3 - 1
src/views/book/courseware/create/components/common/ModuleMixin.js

@@ -78,7 +78,9 @@ const mixin = {
     if (!this.data?.mind_map?.node_list?.[0]?.id) {
       this.data.mind_map = this.data.mind_map ?? {};
       this.data.mind_map.node_list = this.data.mind_map.node_list ?? [{}];
-      this.data.mind_map.node_list[0].id = this.id;
+      if (this.data.mind_map.node_list.length > 0) {
+        this.data.mind_map.node_list[0].id = this.id;
+      }
     }
   },
   methods: {

+ 0 - 2
src/views/book/courseware/create/components/question/character_structure/CharacterStructure.vue

@@ -3,8 +3,6 @@
     <template #content>
       <UploadFile
         key="upload_image"
-        :courseware-id="courseware_id"
-        :component-id="id"
         :type="data.type"
         :file-list="data.image_list"
         :file-id-list="data.image_id_list"

+ 0 - 2
src/views/book/courseware/create/components/question/drawing/Drawing.vue

@@ -3,8 +3,6 @@
     <template #content>
       <UploadFile
         key="upload_image"
-        :courseware-id="courseware_id"
-        :component-id="id"
         :type="data.type"
         :total-size="data.total_size"
         :file-list="data.image_list"

+ 27 - 1
src/views/book/courseware/create/components/question/fill/Fill.vue

@@ -9,6 +9,15 @@
           toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
           :wordlimit-num="false"
         />
+        <el-input
+          v-if="data.property.fill_type === fillTypeList[1].value"
+          v-model="data.vocabulary"
+          type="textarea"
+          :autosize="{ minRows: 2, maxRows: 4 }"
+          resize="none"
+          placeholder="请输入词汇,用于选词填空"
+        />
+
         <span class="tips">在需要加空的内容处插入 3 个或以上的下划线“_”。</span>
         <div v-if="data.audio_file_id">
           <SoundRecord :wav-blob.sync="data.audio_file_id" />
@@ -51,7 +60,7 @@ import ModuleMixin from '../../common/ModuleMixin';
 import SoundRecord from '@/views/book/courseware/create/components/question/fill/components/SoundRecord.vue';
 import UploadAudio from '@/views/book/courseware/create/components/question/fill/components/UploadAudio.vue';
 
-import { getFillData, arrangeTypeList, fillFontList } from '@/views/book/courseware/data/fill';
+import { getFillData, arrangeTypeList, fillFontList, fillTypeList } from '@/views/book/courseware/data/fill';
 import { addTone, handleToneValue } from '@/views/book/courseware/data/common';
 import { getRandomNumber } from '@/utils';
 import { GetStaticResources } from '@/api/app';
@@ -66,11 +75,27 @@ export default {
   data() {
     return {
       data: getFillData(),
+      fillTypeList,
     };
   },
   watch: {
     'data.property.arrange_type': 'handleMindMap',
     'data.property.fill_font': 'handleMindMap',
+    'data.vocabulary': {
+      handler(val) {
+        if (!val) return;
+        this.data.word_list = val
+          .split(/[\n\r]+/)
+          .map((item) => item.split(' ').filter((s) => s))
+          .flat()
+          .map((content) => {
+            return {
+              content,
+              mark: getRandomNumber(),
+            };
+          });
+      },
+    },
   },
   methods: {
     // 识别文本
@@ -105,6 +130,7 @@ export default {
         arr.push({ content: _str.slice(start, index), type: 'text' });
         if (matchNum % 2 === 0 && arr.length > 0) {
           arr[arr.length - 1].type = 'input';
+          arr[arr.length - 1].audio_answer_list = [];
           let mark = getRandomNumber();
           arr[arr.length - 1].mark = mark;
           let content = arr[arr.length - 1].content;

+ 0 - 2
src/views/book/courseware/create/components/question/image_text/ImageText.vue

@@ -3,8 +3,6 @@
     <template #content>
       <UploadFile
         key="upload_image"
-        :courseware-id="courseware_id"
-        :component-id="id"
         :type="data.type"
         :total-size="data.total_size"
         :file-list="data.image_list"

+ 257 - 26
src/views/book/courseware/create/components/question/newWord_template/NewWordTemplate.vue

@@ -1,61 +1,227 @@
 <template>
   <ModuleBase :type="data.type">
     <template #content>
-      <el-button icon="el-icon-plus" style="margin: 24px 0" @click="addElement">增加一个</el-button>
+      <div class="fun-type">
+        <a
+          v-for="{ value, label } in modelList"
+          :key="value"
+          :class="[data.property.model === value ? 'active' : '']"
+          @click="data.property.model = value"
+          >{{ label }}</a
+        >
+      </div>
+      <div class="content-box" v-for="(item, index) in data.option_list" :key="index">
+        <div class="content-item">
+          <el-input
+            v-model="item.content"
+            maxlength="10"
+            show-word-limit
+            placeholder="输入汉字,@:代表图片"
+            @blur="handleMindMap"
+          ></el-input>
+          <el-button @click="identify(item)">识别</el-button>
+        </div>
+        <div class="content-items" v-for="(items, indexs) in item.content_list" :key="indexs">
+          <template v-if="items">
+            <label>内容:{{ items.con }} </label>
+            <UploadFile
+              v-if="items.type === 'img'"
+              :type="data.type"
+              :file-list="items.file_list"
+              :file-id-list="items.file_id_list"
+              :label-text="'图片'"
+              :accept-file-type="acceptFileType"
+              :index="index"
+              :indexs="indexs"
+              :limit="1"
+              @updateFileList="updateFileList"
+            />
+            <div class="option-item">
+              <template v-if="isEnable(data.property.is_enable_pinyin)">
+                <span>拼音</span>
+                <el-input v-model="items.pinyin"></el-input>
+              </template>
+              <template v-if="isEnable(data.property.is_enable_shiyi) && data.property.model === 'input'">
+                <span>释义</span>
+                <el-input v-model="items.shiyi"></el-input>
+              </template>
+              <template v-if="data.property.model === 'miao' && items.type === 'hanzi'">
+                <span>可输入</span>
+                <el-radio-group v-model="items.is_can_input_answer">
+                  <el-radio :label="true">是</el-radio>
+                  <el-radio :label="false">否</el-radio>
+                </el-radio-group>
+              </template>
+
+              <template v-if="data.property.model === 'miao' && items.type === 'hanzi' && items.is_can_input_answer">
+                <span>答案</span>
+                <el-input v-model="items.answer"></el-input>
+                <span>例子</span>
+                <el-radio-group v-model="items.is_example">
+                  <el-radio :label="true">是</el-radio>
+                  <el-radio :label="false">否</el-radio>
+                </el-radio-group>
+              </template>
+            </div>
+          </template>
+        </div>
+      </div>
+      <el-button icon="el-icon-plus" style="margin: 10px 0" @click="addElement">增加一个</el-button>
     </template>
   </ModuleBase>
 </template>
 
 <script>
 import ModuleMixin from '../../common/ModuleMixin';
+import UploadFile from '../../base/common/UploadFile.vue';
 
-import { getNewWordTemplateData } from '@/views/book/courseware/data/newWordTemplate';
+import {
+  getNewWordTemplateData,
+  modelList,
+  answer_list,
+  getOption,
+  isEnable,
+} from '@/views/book/courseware/data/newWordTemplate';
+import { GetStaticResources } from '@/api/app';
 
 export default {
   name: 'NewWordTemplatePage',
-  components: {},
+  components: { UploadFile },
   mixins: [ModuleMixin],
   data() {
     return {
       data: getNewWordTemplateData(),
+      modelList,
+      answer_list,
+      getOption,
+      acceptFileType: '.png,.jpg,.jpeg',
     };
   },
   watch: {
     // 'data.option': 'handleMindMap',
   },
   methods: {
-    // 删除行
-    handleDelete(index) {
-      this.data.option.splice(index, 1);
-    },
-    // 上移下移
-    moveElement(dItem, index, type) {
-      let obj = JSON.parse(JSON.stringify(dItem));
-      if (type == 'up' && index > 0) {
-        this.data.option.splice(index - 1, 0, obj);
-        this.data.option.splice(index + 1, 1);
-      }
-      if (type == 'down' && index < this.data.option.length - 1) {
-        this.data.option[index] = this.data.option.splice(index + 1, 1, this.data.option[index])[0];
-      }
+    updateFileList({ file_list, file_id_list }, index, indexs) {
+      this.data.option_list[index].content_list[indexs].file_list = file_list;
+      this.data.option_list[index].content_list[indexs].file_id_list = file_id_list;
     },
     // 增加
     addElement() {
-      this.data.option.push(getOption());
+      this.data.option_list.push(getOption());
     },
     handleMindMap() {
       // 思维导图数据
       let node_list = [];
-      this.data.option.forEach((item) => {
+      this.data.option_list.forEach((item) => {
         node_list.push({
-          name: item.con.replace(/<[^>]*>?/gm, ''),
+          name: item.content.replace(/<[^>]*>?/gm, ''),
           id: Math.random().toString(36).substring(2, 12),
         });
       });
       this.data.mind_map.node_list = node_list;
     },
-    handleBlurCon() {
-      this.handleMindMap();
+    // 识别
+    async identify(items) {
+      let con = items.content.trim();
+      if (con) {
+        items.content_list = [];
+        let arr = con.split('');
+        const regex = /[\u4E00-\u9FFF]/;
+        let str = '';
+        arr.forEach((item) => {
+          if (regex.test(item)) {
+            str += item;
+          }
+        });
+        let MethodName = 'hz_resource_manager-GetMultHZStrokesContent';
+        let data = {
+          hz_str: str,
+        };
+
+        await GetStaticResources(MethodName, data)
+          .then((res) => {
+            for (let key in res) {
+              if (key != 'status' && key != ',' && res[key]) {
+                res[key] = JSON.parse(res[key]);
+              }
+            }
+            let hzDetailList = res;
+            arr.forEach((item, index) => {
+              let objs = {};
+              if (item === '@') {
+                // 图片
+                objs = {
+                  con: item,
+                  type: 'img',
+                  file_list: [],
+                  file_id_list: [],
+                  pinyin: '',
+                  shiyi: '',
+                  answer: '',
+                  answer_pinyin: '',
+                  answer_en: '',
+                  is_example: false,
+                  is_can_input_answer: true,
+                };
+              } else if (item === '#') {
+                // 书写
+                objs = {
+                  con: item,
+                  type: 'write',
+                  img: '',
+                  base64: '',
+                  pinyin: '',
+                  shiyi: '',
+                  answer: '',
+                  is_example: false,
+                  answer_pinyin: '',
+                  answer_en: '',
+                  is_can_input_answer: true,
+                };
+              } else if (regex.test(item)) {
+                // 汉字
+                let hz_list = [];
+                let res = JSON.parse(JSON.stringify(hzDetailList[item]));
+                let obj = {
+                  con: item,
+                  hzDetail: {
+                    hz_json: res,
+                  },
+                };
+                hz_list.push(obj);
+                objs = {
+                  con: item,
+                  type: 'hanzi',
+                  hz_info: hz_list,
+                  pinyin: cnchar.spell(item, 'array', 'low', 'tone').join(' '),
+                  shiyi: '',
+                  answer: '',
+                  is_example: false,
+                  answer_pinyin: '',
+                  answer_en: '',
+                  is_can_input_answer: true,
+                };
+              } else {
+                // 连字符
+                objs = {
+                  con: item,
+                  type: 'lian',
+                  pinyin: '',
+                  shiyi: '',
+                  answer: '',
+                  answer_pinyin: '',
+                  answer_en: '',
+                  is_example: false,
+                  is_can_input_answer: true,
+                };
+              }
+              this.$set(items.content_list, index, objs);
+            });
+          })
+          .catch(() => {});
+      } else {
+        this.$message.warning('请先输入内容');
+      }
     },
   },
 };
@@ -89,9 +255,74 @@ export default {
     cursor: pointer;
   }
 }
-</style>
-<style lang="scss">
-.tox .tox-editor-header {
-  z-index: 3 !important;
+
+.fun-type {
+  display: flex;
+  gap: 5px;
+  width: 100%;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #e5e6eb;
+
+  a {
+    padding: 5px 10px;
+    font-weight: normal;
+    color: #1d2129;
+    cursor: pointer;
+    background: #f2f3f5;
+    border: 1px solid #f2f3f5;
+    border-radius: 2px;
+
+    &.active {
+      color: #165dff;
+      background: #e7eeff;
+      border-color: #165dff;
+    }
+  }
+}
+
+.content-box {
+  margin: 10px 0;
+}
+
+.content-item {
+  display: flex;
+  gap: 16px;
+
+  .el-input {
+    max-width: 400px;
+  }
+}
+
+.content-items {
+  margin: 10px 0;
+
+  label {
+    font-size: 14px;
+    line-height: 34px;
+    color: #4e5969;
+  }
+
+  .option-item {
+    display: flex;
+    align-items: center;
+    margin-top: 5px;
+
+    span {
+      flex-shrink: 0;
+      width: max-content;
+      margin-right: 10px;
+      font-size: 14px;
+      color: #4e5969;
+    }
+
+    .el-input {
+      max-width: 100px;
+      margin-right: 30px;
+    }
+
+    .el-radio-group {
+      margin-right: 30px;
+    }
+  }
 }
 </style>

+ 31 - 1
src/views/book/courseware/create/components/question/newWord_template/NewWordTemplateSetting.vue

@@ -2,6 +2,34 @@
   <div>
     <el-form :model="property" label-width="72px" label-position="left">
       <SerailNumber :property="property" />
+      <el-form-item label="播放笔顺">
+        <el-radio-group v-model="property.is_enable_play_structure">
+          <el-radio v-for="{ value, label } in showList" :key="value" :label="value">
+            {{ label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="拼音">
+        <el-radio-group v-model="property.is_enable_pinyin">
+          <el-radio v-for="{ value, label } in showList" :key="value" :label="value">
+            {{ label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="标红笔画">
+        <el-radio-group v-model="property.is_enable_high_strokes">
+          <el-radio v-for="{ value, label } in showList" :key="value" :label="value">
+            {{ label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="释义" v-if="property.model === 'input'">
+        <el-radio-group v-model="property.is_enable_shiyi">
+          <el-radio v-for="{ value, label } in showList" :key="value" :label="value">
+            {{ label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
     </el-form>
   </div>
 </template>
@@ -9,7 +37,7 @@
 <script>
 import SettingMixin from '@/views/book/courseware/create/components/common/SettingMixin';
 
-import { getNewWordTemplateProperty } from '@/views/book/courseware/data/newWordTemplate';
+import { getNewWordTemplateProperty, showList, displayList } from '@/views/book/courseware/data/newWordTemplate';
 
 export default {
   name: 'NewWordTemplateSetting',
@@ -17,6 +45,8 @@ export default {
   data() {
     return {
       property: getNewWordTemplateProperty(),
+      showList,
+      displayList,
     };
   },
   methods: {},

+ 0 - 2
src/views/book/courseware/create/components/question/video_interaction/VideoInteraction.vue

@@ -3,8 +3,6 @@
     <template #content>
       <UploadFile
         key="upload_image"
-        :courseware-id="courseware_id"
-        :component-id="id"
         :type="data.type"
         :total-size="data.total_size"
         :file-list="data.video_list"

+ 7 - 2
src/views/book/courseware/create/components/question/voice_matrix/CheckSubtitles.vue

@@ -10,6 +10,7 @@
             <div class="lrc-time">
               <span>[{{ audioTimeToMMSS(lrc_data.begin_time) }}]</span>
             </div>
+            <!-- eslint-disable-next-line vue/no-v-html -->
             <span class="lrc-text" v-html="content"></span>
           </div>
         </template>
@@ -53,6 +54,7 @@
 <script>
 import { GetFileURLMap } from '@/api/app';
 import { secondFormatConversion, audioTimeToMMSS } from '@/utils/transform';
+import { getArrayDepth } from '@/utils/common';
 
 export default {
   name: 'CheckSubtitles',
@@ -92,6 +94,7 @@ export default {
       audio_allTime: null, // 展示总时间
       secondFormatConversion,
       audioTimeToMMSS,
+      getArrayDepth,
     };
   },
   computed: {
@@ -118,7 +121,8 @@ export default {
     },
     optionList: {
       handler(val) {
-        this.dataList = JSON.parse(JSON.stringify(val));
+        let _val = JSON.parse(JSON.stringify(val));
+        this.dataList = this.getArrayDepth(_val) === 1 ? [_val] : _val;
       },
       immediate: true,
       deep: true,
@@ -244,7 +248,8 @@ export default {
       this.audio.paused = false;
     },
     saveSubtitles() {
-      this.$emit('saveSubtitles', this.dataList);
+      const subtitles = this.getArrayDepth(this.optionList) === 1 ? this.dataList.flat() : this.dataList;
+      this.$emit('saveSubtitles', subtitles);
       this.$message.success('字幕已保存');
     },
   },

+ 0 - 1
src/views/book/courseware/create/components/question/write/Write.vue

@@ -9,7 +9,6 @@
         </template>
         <template v-else>
           <UploadFile
-            :courseware-id="courseware_id"
             :type="'picture'"
             :single-size="data.single_size"
             :total-size="data.total_size"

+ 8 - 8
src/views/book/courseware/data/bookType.js

@@ -407,14 +407,14 @@ export const bookTypeOption = [
         set: DrawingSetting,
         preview: DrawingPreview,
       },
-      // {
-      //   value: 'newWord_template',
-      //   label: '生字',
-      //   icon: '',
-      //   component: NewWordTemplate,
-      //   set: NewWordTemplateSetting,
-      //   preview: NewWordTemplatePreview,
-      // },
+      {
+        value: 'newWord_template',
+        label: '生字',
+        icon: '',
+        component: NewWordTemplate,
+        set: NewWordTemplateSetting,
+        preview: NewWordTemplatePreview,
+      },
       {
         value: 'character_structure',
         label: '汉字结构',

+ 3 - 0
src/views/book/courseware/data/fill.js

@@ -69,8 +69,11 @@ export function getFillData() {
     audio_file_id: '',
     record_list: [],
     model_essay: [],
+    vocabulary: '', // 用于选词的词汇
+    word_list: [], // 选词列表
     answer: {
       answer_list: [],
+      reference_answer: '',
     },
     mind_map: {
       node_list: [{ name: '横排中文填空组件' }],

+ 0 - 66
src/views/book/courseware/data/newWordTemplate copy.js

@@ -1,66 +0,0 @@
-import {
-  displayList,
-  serialNumberTypeList,
-  serialNumberPositionList,
-  arrangeTypeList,
-  switchOption,
-  isEnable,
-} from '@/views/book/courseware/data/common';
-
-export { arrangeTypeList, switchOption, isEnable };
-
-// 显示
-export const showList = [
-  {
-    value: 'true',
-    label: '显示',
-  },
-  {
-    value: 'false',
-    label: '不显示',
-  },
-];
-
-// 汉字框
-export const frameList = [
-  {
-    value: 'tian',
-    label: '田字格',
-  },
-  {
-    value: 'fang',
-    label: '方框',
-  },
-  {
-    value: 'none',
-    label: '无',
-  },
-];
-
-export function getNewWordTemplateProperty() {
-  return {
-    serial_number: 1,
-    sn_type: serialNumberTypeList[0].value,
-    sn_position: serialNumberPositionList[3].value,
-    sn_display_mode: displayList[0].value,
-
-    is_enable_pinyin: showList[0].value,
-    
-  };
-}
-
-export function getNewWordTemplateData() {
-  return {
-    type: 'newWord_template',
-    title: '生字',
-    property: getNewWordTemplateProperty(),
-    content: '',
-    content_list: [],
-    mind_map: {
-      node_list: [{ name: '生字' }], // 思维导图数据
-    },
-    answer: {
-      answer_list: [],
-    },
-  };
-}

+ 51 - 3
src/views/book/courseware/data/newWordTemplate.js

@@ -6,8 +6,9 @@ import {
   switchOption,
   isEnable,
 } from '@/views/book/courseware/data/common';
+import { getRandomNumber } from '@/utils';
 
-export { arrangeTypeList, switchOption, isEnable };
+export { arrangeTypeList, switchOption, isEnable, displayList };
 
 // 显示
 export const showList = [
@@ -37,6 +38,45 @@ export const frameList = [
   },
 ];
 
+// 模式类型
+export const modelList = [
+  {
+    value: 'miao',
+    label: '标红笔画',
+  },
+  {
+    value: 'input',
+    label: '输入拼音释义',
+  },
+];
+
+// 答题方式
+export const answer_list = [
+  {
+    value: 'pinyin',
+    label: '填拼音',
+  },
+  {
+    value: 'en',
+    label: '填英文',
+  },
+];
+
+export function getOption() {
+  return {
+    content: '',
+    pinyin: '',
+    mark: getRandomNumber(),
+    is_example: false,
+    answer: '',
+    hz_info: [],
+    file_list: [],
+    file_id_list: [],
+    content_list: [],
+    is_common_pinyin: false
+  };
+}
+
 export function getNewWordTemplateProperty() {
   return {
     serial_number: 1,
@@ -44,7 +84,15 @@ export function getNewWordTemplateProperty() {
     sn_position: serialNumberPositionList[3].value,
     sn_display_mode: displayList[0].value,
 
+    is_enable_play_structure: showList[0].value,
     is_enable_pinyin: showList[0].value,
+    is_enable_high_strokes: showList[1].value,
+    model:modelList[0].value,
+    
+    // miao模式
+
+    // input模式
+    is_enable_shiyi: showList[0].value,
     
   };
 }
@@ -54,8 +102,8 @@ export function getNewWordTemplateData() {
     type: 'newWord_template',
     title: '生字',
     property: getNewWordTemplateProperty(),
-    content: '',
-    content_list: [],
+    option_list: [getOption()],
+    answer_type:'',
     mind_map: {
       node_list: [{ name: '生字' }], // 思维导图数据
     },

+ 5 - 1
src/views/book/courseware/data/voiceMatrix.js

@@ -33,7 +33,11 @@ export function getOption() {
   return {
     content: '',
     mark: getRandomNumber(),
-    lrc_data: {}, // lrc 数据
+    lrc_data: {
+      begin_time: 0,
+      end_time: 0,
+      text: '',
+    },
   };
 }
 

+ 1 - 2
src/views/book/courseware/data/write.js

@@ -81,8 +81,7 @@ export function getWriteData() {
     // 内容中包含的文件列表,
     file_list: [],
     mind_map: {
-      node_list: [
-      ], // 思维导图数据
+      node_list: [{ name: '书写组件' }], // 思维导图数据
     },
     answer: {
       answer_list: [],

+ 2 - 0
src/views/book/courseware/preview/common/SoundRecord.vue

@@ -17,6 +17,7 @@
         @click="playmicrophone(selectIndex || selectIndex == 0 ? recordList[selectIndex].toltime : '')"
       ></div>
     </template>
+
     <template v-else-if="type === 'pro'">
       <div :class="['record', microphoneStatus ? 'active' : '']" @click="microphone"></div>
       <el-select v-model="selectIndex" placeholder="无录音" class="proSelect" @change="handleChangeRecord">
@@ -28,6 +29,7 @@
       ></div>
       <a :class="['record-delete', hasMicro ? 'record-delete-has' : '']" @click="handleDelete"></a>
     </template>
+
     <template v-else>
       <div :class="['record', microphoneStatus ? 'active' : '']" @click="microphone"></div>
       <span

+ 134 - 24
src/views/book/courseware/preview/components/fill/FillPreview.vue

@@ -6,7 +6,7 @@
     <div class="main" :style="getMainStyle()">
       <AudioFill :file-id="data.audio_file_id" />
       <div class="fill-wrapper">
-        <p v-for="(item, i) in data.model_essay" :key="i">
+        <p v-for="(item, i) in modelEssay" :key="i">
           <template v-for="(li, j) in item">
             <span v-if="li.type === 'text'" :key="j" v-html="sanitizeHTML(li.content)"></span>
             <template v-if="li.type === 'input'">
@@ -21,10 +21,22 @@
               </template>
 
               <template v-else-if="data.property.fill_type === fillTypeList[1].value">
-                <el-popover :key="j" placement="top" trigger="click" content="">
+                <el-popover :key="j" placement="top" trigger="click">
+                  <div class="word-list">
+                    <span
+                      v-for="{ content, mark } in data.word_list"
+                      :key="mark"
+                      class="word-item"
+                      @click="handleSelectWord(content, mark, li)"
+                    >
+                      {{ content }}
+                    </span>
+                  </div>
+
                   <el-input
+                    slot="reference"
                     v-model="li.content"
-                    :disabled="true"
+                    :readonly="true"
                     :class="[data.property.fill_font, ...computedAnswerClass(li.mark)]"
                     class="pinyin"
                     :style="[{ width: Math.max(80, li.content.length * 21.3) + 'px' }]"
@@ -32,11 +44,30 @@
                 </el-popover>
               </template>
 
-              <template v-else-if="data.property.fill_type === fillTypeList[2].value"></template>
+              <template v-else-if="data.property.fill_type === fillTypeList[2].value">
+                <span :key="j" class="write-click" @click="handleWriteClick(li.mark)">
+                  <img
+                    v-show="li.write_base64"
+                    style="background-color: #f4f4f4"
+                    :src="li.write_base64"
+                    alt="write-show"
+                  />
+                </span>
+              </template>
 
               <template v-else-if="data.property.fill_type === fillTypeList[3].value">
-                <SoundRecord :key="j" :wav-blob.sync="li.audio_file_id" />
+                <SoundRecordBox
+                  ref="record"
+                  :key="j"
+                  type="mini"
+                  :many-times="false"
+                  class="record-box"
+                  :answer-record-list="data.audio_answer_list"
+                  :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
+                  @handleWav="handleMiniWav($event, li.mark)"
+                />
               </template>
+
               <span v-show="computedAnswerText(li.mark).length > 0" :key="`answer-${j}`" class="right-answer">
                 {{ computedAnswerText(li.mark) }}
               </span>
@@ -54,6 +85,8 @@
         @handleWav="handleWav"
       />
     </div>
+
+    <WriteDialog :visible.sync="writeVisible" @confirm="handleWriteConfirm" />
   </div>
 </template>
 
@@ -69,18 +102,26 @@ import {
 import PreviewMixin from '../common/PreviewMixin';
 import AudioFill from './components/AudioFillPlay.vue';
 import SoundRecord from '../../common/SoundRecord.vue';
+import SoundRecordBox from '@/views/book/courseware/preview/components/record_input/SoundRecord.vue';
+import WriteDialog from './components/WriteDialog.vue';
 
 export default {
   name: 'FillPreview',
   components: {
     AudioFill,
     SoundRecord,
+    SoundRecordBox,
+    WriteDialog,
   },
   mixins: [PreviewMixin],
   data() {
     return {
       data: getFillData(),
       fillTypeList,
+      modelEssay: [],
+      selectedWordList: [], // 用于存储选中的词汇
+      writeVisible: false,
+      writeMark: '',
     };
   },
   computed: {
@@ -91,15 +132,19 @@ export default {
   watch: {
     'data.model_essay': {
       handler(list) {
+        if (!list || !Array.isArray(list)) return;
+
+        this.modelEssay = JSON.parse(JSON.stringify(list));
         this.answer.answer_list = list
           .map((item) => {
             return item
-              .map(({ type, content, mark }) => {
+              .map(({ type, content, audio_answer_list, mark }) => {
                 if (type === 'input') {
                   return {
                     value: content,
-                    audio_file_id: '',
                     mark,
+                    audio_answer_list,
+                    write_base64: '',
                   };
                 }
               })
@@ -108,12 +153,13 @@ export default {
           .flat();
       },
       deep: true,
+      immediate: true,
     },
     isJudgingRightWrong(val) {
       if (!val) return;
 
       this.answer.answer_list.forEach(({ mark, value }) => {
-        this.data.model_essay.forEach((item) => {
+        this.modelEssay.forEach((item) => {
           item.forEach((li) => {
             if (li.mark === mark) {
               li.content = value;
@@ -128,26 +174,55 @@ export default {
       this.answer.record_list = val;
     },
   },
-  created() {
-    this.answer.answer_list = this.data.model_essay
-      .map((item) => {
-        return item
-          .map(({ type, content, mark }) => {
-            if (type === 'input') {
-              return {
-                value: content,
-                mark,
-              };
-            }
-          })
-          .filter((item) => item);
-      })
-      .flat();
-  },
   methods: {
     handleWav(data) {
       this.data.record_list = data;
     },
+    /**
+     * 处理小音频录音
+     * @param {Object} data 音频数据
+     * @param {String} mark 选项标识
+     */
+    handleMiniWav(data, mark) {
+      if (!data || !mark) return;
+      for (const item of this.modelEssay) {
+        const li = item.find((li) => li?.mark === mark);
+        if (li) {
+          this.$set(li, 'audio_answer_list', data);
+          break;
+        }
+      }
+    },
+    /**
+     * 处理选中词汇
+     * @param {String} content 选中的词汇内容
+     * @param {String} mark 选项标识
+     * @param {Object} li 当前输入框对象
+     */
+    handleSelectWord(content, mark, li) {
+      if (!content || !mark || !li) return;
+
+      li.content = content;
+      this.selectedWordList.push(mark);
+    },
+    /**
+     * 处理书写区确认
+     * @param {String} data 书写区数据
+     */
+    handleWriteConfirm(data) {
+      if (!data) return;
+      for (const item of this.modelEssay) {
+        const li = item.find((li) => li?.mark === this.writeMark);
+        if (li) {
+          this.$set(li, 'write_base64', data);
+          break;
+        }
+      }
+    },
+    handleWriteClick(mark) {
+      this.writeVisible = true;
+      this.writeMark = mark;
+    },
     getMainStyle() {
       const isRow = this.data.property.arrange_type === arrangeTypeList[0].value;
       const isFront = this.data.property.audio_position === audioPositionList[0].value;
@@ -234,6 +309,28 @@ export default {
       margin: 0;
     }
 
+    .record-box {
+      display: inline-flex;
+      align-items: center;
+      background-color: #fff;
+      border-bottom: 1px solid $font-color;
+    }
+
+    .write-click {
+      display: inline-block;
+      width: 104px;
+      height: 32px;
+      padding: 0 4px;
+      vertical-align: bottom;
+      cursor: pointer;
+      border-bottom: 1px solid $font-color;
+
+      img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
     .el-input {
       display: inline-flex;
       align-items: center;
@@ -297,3 +394,16 @@ export default {
   }
 }
 </style>
+
+<style lang="scss" scoped>
+.word-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  align-items: center;
+
+  .word-item {
+    cursor: pointer;
+  }
+}
+</style>

+ 83 - 0
src/views/book/courseware/preview/components/fill/components/WriteDialog.vue

@@ -0,0 +1,83 @@
+<template>
+  <el-dialog :visible="visible" title="手写区" width="560px" :close-on-click-modal="false" @close="dialogClose">
+    <div class="esign-wrapper">
+      <VueEsign
+        ref="esign"
+        :width="540"
+        :height="180"
+        :is-crop="isCrop"
+        :line-width="lineWidth"
+        :line-color="lineColor"
+        :bg-color.sync="bgColor"
+      />
+      <i class="el-icon-check" @click="handleGenerate"></i>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import VueEsign from 'vue-esign';
+
+export default {
+  name: 'WriteDialog',
+  components: {
+    VueEsign,
+  },
+  props: {
+    visible: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      lineWidth: 2,
+      lineColor: '#000000',
+      bgColor: '#F4F4F4',
+      isCrop: false,
+    };
+  },
+  watch: {
+    visible(val) {
+      if (!val) {
+        this.$refs.esign.reset();
+        this.$nextTick(() => {
+          this.bgColor = '#F4F4F4';
+        });
+      }
+    },
+  },
+  methods: {
+    dialogClose() {
+      this.$emit('update:visible', false);
+      this.$emit('close');
+    },
+    handleGenerate() {
+      this.$refs.esign
+        .generate({ format: 'png', quality: 0.8 })
+        .then((data) => {
+          this.$emit('confirm', data);
+          this.dialogClose();
+        })
+        .catch((err) => {
+          console.error(err);
+        });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.esign-wrapper {
+  position: relative;
+
+  i {
+    position: absolute;
+    right: 6px;
+    bottom: 6px;
+    padding: 6px;
+    cursor: pointer;
+    background-color: #e5e6eb;
+  }
+}
+</style>

+ 64 - 855
src/views/book/courseware/preview/components/newWord_template/NewWordTemplatePreview.vue

@@ -1,9 +1,38 @@
 <!-- eslint-disable vue/no-v-html -->
 <template>
-  <div class="newWord-preview" :style="getAreaStyle()">
+  <div class="newWord-template-preview" :style="getAreaStyle()">
     <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
 
-    <div class="main"></div>
+    <div class="main">
+      <div class="item-box" v-for="(item, index) in data.option_list" :key="index">
+        <div class="number-box">
+          <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>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -11,11 +40,11 @@
 import { getNewWordTemplateData } from '@/views/book/courseware/data/newWordTemplate';
 
 import PreviewMixin from '../common/PreviewMixin';
-
+import Strockplayredline from './components/Strockplayredline.vue';
 export default {
   name: 'NewWordPreview',
 
-  components: {},
+  components: { Strockplayredline },
   mixins: [PreviewMixin],
   data() {
     return {
@@ -39,876 +68,56 @@ export default {
 <style lang="scss" scoped>
 @use '@/styles/mixin.scss' as *;
 
-.newWord-preview {
-  @include preview-base;
-
-  .NPC-zhedie {
-    width: 1007px;
-
-    // width: 780px;
-    margin-bottom: 24px;
-
-    .aduioLine-box {
-      margin-bottom: 8px;
-    }
-
-    .practiceBox {
-      position: fixed;
-      top: 0;
-      left: 0;
-      z-index: 999;
-      box-sizing: border-box;
-      width: 100%;
-      height: 100vh;
-      overflow: hidden;
-      overflow-y: auto;
-      background: rgba(0, 0, 0, 19%);
-    }
-
-    .NPC-word-list {
-      overflow: auto;
-      background: #f7f7f7;
-
-      .NPC-word-tab-common,
-      .collocation,
-      .tabNum-box {
-        flex-shrink: 0;
-      }
-    }
-
-    .NPC-word-table {
-      width: 100%;
-
-      :deep p {
-        margin: 0;
-      }
-
-      > .NPC-word-tr {
-        margin-bottom: 8px;
-        background: #fff;
-        border-radius: 8px;
-
-        .NPC-word-row {
-          position: relative;
-          display: flex;
-
-          // flex-flow: wrap;
-          justify-content: flex-start;
-          padding: 8px 13px 8px 12px;
-          padding-right: 80px;
-          cursor: pointer;
-          border-radius: 8px;
-
-          &.active {
-            background: linear-gradient(0deg, rgba(0, 0, 0, 8%), rgba(0, 0, 0, 8%)), #fff;
-          }
-
-          .right-box {
-            position: absolute;
-            top: 8px;
-            right: 5px;
-            display: flex;
-            gap: 5px;
-          }
-
-          > span {
-            font-size: 16px;
-            line-height: 150%;
-            color: #000;
-          }
-        }
-
-        .NPC-word-tab-common {
-          box-sizing: border-box;
-          width: 125px;
-          padding-left: 8px;
-        }
-      }
-    }
-  }
-
-  .NPC-word-list {
-    padding: 20px 24px;
-    border: 1px solid rgba(0, 0, 0, 10%);
-    border-top: none;
-    border-radius: 0 0 8px 8px;
-  }
-
-  .detail-icon {
-    display: block;
-    width: 24px;
-    height: 24px;
-    cursor: pointer;
-    opacity: 0.5;
-  }
-
-  .tabNum-box {
-    position: relative;
-
-    .star-label {
-      position: absolute;
-      top: 1px;
-      right: -6px;
-      width: 6px;
-      height: 6px;
-    }
-  }
-
-  .play-btn {
-    display: block;
-    width: 16px;
-    height: 16px;
-    margin-top: 4px;
-    background: url('@/assets/fill/voice-pause-red.png') no-repeat left top;
-    background-size: 100% 100%;
-
-    &.active {
-      background: url('@/assets/fill/voice-play-red.png') no-repeat left top;
-      background-size: 100% 100%;
-    }
-  }
-
-  .tabNum {
-    display: block;
-    width: 16px;
-    height: 16px;
-    margin-top: 4px;
-    margin-left: 8px;
-    font-family: 'robot', 'alabo';
-    font-size: 12px;
-    line-height: 16px;
-    color: #fff;
-    text-align: center;
-    background: #de4444;
-    border-radius: 50%;
-  }
-
-  .NPC-word-tab-box {
-    width: 240px;
-
-    span {
-      display: block;
-      width: 100%;
-      margin: 2px 0;
-      color: #000;
-    }
-  }
-
-  .NPC-word-tab-pinyin {
-    font-family: 'League';
-
-    // white-space: nowrap;
-    font-size: 12px;
-    word-break: break-word;
-
-    &.NPC-word-tab-pinyin-red {
-      color: #e35454;
-    }
-
-    &.NPC-word-tab-pinyin-green {
-      color: #24b99e;
-    }
-
-    &.NPC-word-tab-pinyin-brown {
-      color: #bd8865;
-    }
-  }
-
-  .NPC-word-tab-word {
-    font-family: '楷体';
-    font-size: 16px;
-    white-space: nowrap;
-
-    &.NPC-word-tab-word-red {
-      color: #e35454;
-    }
-
-    &.NPC-word-tab-word-green {
-      color: #24b99e;
-    }
-
-    &.NPC-word-tab-word-brown {
-      color: #bd8865;
-    }
-
-    &-break {
-      word-break: break-word;
-      white-space: normal;
-    }
-  }
-
-  .NPC-word-tab-cixing {
-    box-sizing: border-box;
-
-    // width: 48px;
-    width: 60px;
-    font-family: 'robot', 'alabo';
-    text-align: left;
-    word-break: break-word;
-
-    // font-style: italic;  // 要求改为正体
-    &.NPC-word-tab-cixing-red {
-      color: #e35454;
-    }
-
-    &.NPC-word-tab-cixing-green {
-      color: #24b99e;
-    }
-
-    &.NPC-word-tab-cixing-brown {
-      color: #bd8865;
-    }
-
-    &.hasCn {
-      font-size: 13px;
-    }
-  }
-
-  .NPC-word-tab-def {
-    box-sizing: border-box;
-
-    // flex: 1;
-    font-family: 'robot', 'alabo';
-    word-break: break-word;
-    white-space: pre-wrap;
-
-    &.NPC-word-tab-def-red {
-      color: #e35454;
-    }
-
-    &.NPC-word-tab-def-green {
-      color: #24b99e;
-    }
-
-    &.NPC-word-tab-def-brown {
-      color: #bd8865;
-    }
+.newWord-template-preview {
+  .item-box {
+    display: flex;
+    align-items: end;
   }
 
-  .collocation {
+  .number-box {
     display: flex;
-    width: 100%;
-
-    // padding-top: 8px;
-
-    > span {
-      flex-shrink: 0;
-      font-size: 16px;
-      font-weight: 400;
-      line-height: 24px;
-      color: #000;
-    }
+    align-items: center;
+    height: 80px;
+    margin-right: 16px;
 
-    > div b {
+    .number {
       display: block;
-    }
-
-    > b,
-    > div b {
-      flex: 1;
-      font-family: 'robot', '楷体', 'alabo';
-      font-size: 16px;
-      font-weight: 400;
+      width: 24px;
+      height: 24px;
+      font-family: 'arial';
+      font-size: 14px;
+      font-weight: bold;
       line-height: 24px;
-      color: rgba(0, 0, 0, 65%);
-    }
-  }
-
-  @keyframes firstrotate {
-    0% {
-      transform: rotateZ(0deg);
-    }
-
-    100% {
-      transform: rotateZ(180deg);
-    }
-  }
-
-  @keyframes huifuRotate {
-    0% {
-      transform: rotateZ(180deg);
-    }
-
-    100% {
-      transform: rotateZ(0deg);
-    }
-  }
-
-  .luyin-box-wordphrase {
-    height: 24px;
-  }
-
-  .NPC-word-tile {
-    display: flex;
-    flex-flow: wrap;
-    gap: 20px;
-    padding: 20px 0;
-  }
-
-  .writeTop {
-    position: relative;
-    display: flex;
-    column-gap: 8px;
-    width: 400px;
-
-    .left,
-    .right {
-      position: relative;
-      box-sizing: border-box;
-      width: 100%;
-      min-height: 270px;
-      padding: 8px 12px 18px;
-      overflow: hidden;
-      background: #fff;
-      border: 4px solid #fff;
-      border-radius: 24px;
-
-      .header-info {
-        display: flex;
-        justify-content: space-between;
-        width: 100%;
-        margin-bottom: 12px;
-
-        :deep .el-input__inner {
-          height: 24px;
-          padding: 0;
-          font-size: 24px;
-          font-weight: 400;
-          line-height: 100%;
-          color: rgba(0, 0, 0, 100%);
-          border: none;
-        }
-
-        .label {
-          :deep .el-input__inner {
-            text-align: right;
-          }
-        }
-      }
-    }
-
-    min-height: 332px;
-    transition: 0.6s;
-    perspective: 1000px;
-    transform-style: preserve-3d;
-
-    .left-preview {
-      padding-top: 40px;
-
-      // padding-bottom: 32px;
-      // position: absolute;
-      backface-visibility: hidden;
-    }
-
-    .header-info-preview {
-      position: absolute;
-      top: 0;
-      left: 0;
-      z-index: 1;
-      width: 100%;
-
-      h5 {
-        padding: 0 12px;
-        margin: 0;
-        font-size: 20px;
-        font-weight: 400;
-        line-height: 32px;
-        color: #000;
-      }
-
-      label {
-        position: absolute;
-        top: -4px;
-        right: -4px;
-        padding: 0 16px 0 8px;
-        font-size: 20px;
-        font-weight: 500;
-        line-height: 150%;
-        color: #fff;
-        background: #fff;
-        border-radius: 0 8px;
-      }
-    }
-
-    .left-big {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-
-    .del-btn {
-      position: absolute;
-      right: 8px;
-      bottom: 8px;
-      padding: 5px 8px;
-      font-size: 24px;
-      color: #fff;
-      cursor: pointer;
-      background: #f56767;
-      border-radius: 40px;
-    }
-
-    .overturn-btn {
-      position: absolute;
-      right: 8px;
-      bottom: 8px;
-      width: 40px;
-      height: 40px;
-      padding: 8px;
-      font-size: 24px;
-      line-height: 1;
       color: #fff;
-      cursor: pointer;
-      background: #e0e0e0;
-      border-radius: 8px;
-    }
-
-    .el-icon-zoom-in,
-    .filt-check {
-      position: absolute;
-      bottom: 8px;
-      left: 8px;
-      width: 30px;
-      height: 30px;
-      font-size: 24px;
-      cursor: pointer;
-
-      :deep .el-checkbox__inner {
-        width: 24px;
-        height: 24px;
-      }
-
-      :deep .el-checkbox__inner::after {
-        left: 8px;
-        width: 6px;
-        height: 14px;
-      }
-    }
-
-    .right {
-      display: flex;
-      flex-flow: wrap;
-      row-gap: 8px;
-      align-items: center;
-      padding: 16px 24px 26px;
-
-      .card-label {
-        width: 100%;
-        height: 22px;
-        font-size: 14px;
-        font-weight: 400;
-        line-height: 22px;
-        color: #4e5969;
-      }
-
-      :deep .el-textarea {
-        height: 64px;
-      }
-
-      .config-box {
-        display: flex;
-        align-items: center;
-        width: 100%;
-
-        span {
-          margin-right: 8px;
-          font-size: 14px;
-          line-height: 20px;
-          color: #000;
-        }
-
-        .el-color-picker {
-          height: 32px;
-        }
-
-        :deep .el-color-picker__trigger {
-          height: 32px;
-        }
-
-        .el-radio {
-          margin-right: 8px;
-        }
-
-        .el-radio-group {
-          display: flex;
-        }
-
-        :deep .el-radio__input.is-checked .el-radio__inner {
-          background: #000;
-          border-color: #000;
-        }
-
-        :deep .el-radio__input.is-checked + .el-radio__label {
-          color: #000;
-        }
-      }
-    }
-
-    .right-preview {
-      display: block;
-      padding: 36px;
-
-      .pinyin-box {
-        margin-bottom: 8px;
-        font-family: 'League';
-        font-feature-settings: 'cv01' on;
-
-        // font-size: 18px;
-        line-height: 120%;
-        color: #de4444;
-        text-align: center;
-      }
-
-      .hz-box {
-        width: 100%;
-
-        .hz-item {
-          text-align: center;
-
-          :deep .strockplayInner {
-            width: 76px;
-            height: 76px;
-          }
-
-          p {
-            margin: 0 0 8px;
-            font-family: 'League';
-            font-size: 18px;
-            font-feature-settings: 'cv01' on;
-            line-height: 120%;
-            color: #de4444;
-          }
-        }
-      }
-
-      :deep .audio-wrapper {
-        box-sizing: border-box;
-        width: 50px;
-        height: 50px;
-        padding: 13px;
-        margin: 0 auto 8px;
-        cursor: pointer;
-        background: #f3f3f3;
-        border-radius: 40px;
-
-        .voice-play {
-          width: 24px;
-          height: 24px;
-        }
-      }
-
-      .definition_list-box {
-        margin-top: 16px;
-        white-space: pre;
-
-        > div {
-          display: flex;
-          margin-bottom: 8px;
-
-          label,
-          p {
-            width: 40px;
-            font-size: 14px;
-            font-weight: 400;
-            line-height: 150%;
-            color: #000;
-          }
-
-          label {
-            width: 47px;
-          }
-
-          p {
-            flex: 1;
-            line-height: 0;
-            word-break: break-word;
-            white-space: pre-wrap;
-          }
-        }
-      }
-
-      :deep p {
-        margin: 0;
-        line-height: 1.5;
-      }
-    }
-
-    .right-preview-rota {
-      transform: rotateY(180deg);
-    }
-
-    .item-image {
-      position: relative;
-      overflow: hidden;
-      font-size: 0;
-
-      // background: #f2f3f5;
-      border-radius: 8px;
-
-      .item-image-del {
-        position: absolute;
-        top: 8px;
-        right: 8px;
-        display: block;
-        width: 16px;
-        height: 16px;
-        padding: 8px;
-        font-size: 16px;
-        color: #ee3232;
-        cursor: pointer;
-        background-color: #fff;
-        border-radius: 50%;
-        box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 25%);
-      }
-    }
-
-    .item-con {
-      display: flex;
-      align-items: center;
-      width: 50%;
-      margin-top: 16px;
-
-      label {
-        width: 44px;
-        font-size: 14px;
-        font-weight: 400;
-        line-height: 22px;
-        color: #4e5969;
-      }
-
-      :deep .el-input__inner {
-        width: 235px;
-        height: 32px;
-        font-family: '楷体';
-        font-size: 14px;
-        font-weight: 400;
-        line-height: 22px;
-        background: #f2f3f5;
-        border: none;
-        border-radius: 2px;
-      }
-
-      .pinyin {
-        :deep .el-input__inner {
-          font-family: 'League';
-        }
-      }
-    }
-
-    .con-preview {
-      margin-top: 8px;
-      font-family: '楷体';
-      font-size: 38px;
-      font-weight: 400;
-      line-height: 100%;
-      color: #000;
       text-align: center;
-
-      &-big {
-        margin: 0;
-        font-size: 86px;
-      }
-    }
-
-    .writeTop-row {
-      display: flex;
-      justify-content: center;
+      background: #346cda;
+      border-radius: 100%;
     }
   }
 
-  .flipped {
-    transform: rotateY(180deg);
-  }
-
-  .flipped-back {
-    transform: rotateY(180deg);
-  }
-
-  .hz-box {
-    display: flex;
-    width: max-content;
-  }
-
-  .writeTop-item {
-    border: 1px solid #de4444;
-  }
-
-  .writeTop-item-noLeft {
-    border-left: none;
-  }
-}
-
-.newWord-table {
-  .cell {
-    width: max-content;
-    white-space: nowrap;
+  .items {
+    font-size: 0;
   }
 
-  :deep thead {
-    display: none;
-  }
-}
-</style>
-<style lang="scss">
-.NPC-zhedie {
-  .topTitle {
-    display: flex;
-    justify-content: space-between;
-    width: 100%;
-    height: 48px;
-    padding-right: 16px;
-    padding-left: 24px;
+  .items-image,
+  .hanzi-storck {
+    width: 80px;
+    height: 80px;
     overflow: hidden;
-    background: #e35454;
-    border: 1px solid rgba(0, 0, 0, 10%);
-    border-radius: 8px 8px 0 0;
-
-    .NPC-top-left {
-      display: flex;
-      align-items: center;
-      justify-content: flex-start;
-
-      .NPC-topTitle-text {
-        margin-right: 8px;
-        font-family: 'sourceR';
-        font-size: 16px;
-        font-weight: bold;
-        color: #fff;
-        white-space: pre;
-      }
-    }
-
-    .NPC-top-right {
-      display: flex;
-      gap: 4px;
-      align-items: center;
-      justify-content: flex-start;
-      cursor: pointer;
-
-      &-text {
-        font-size: 14px;
-        font-weight: normal;
-        line-height: 16px;
-        color: #fff;
-      }
-
-      img {
-        width: 16px;
-        height: 16px;
-      }
-    }
-
-    img {
-      width: 24px;
-      height: 24px;
-    }
-
-    .rotate {
-      animation-name: firstrotate;
-      animation-timing-function: linear;
-      animation-direction: 2s;
-      animation-fill-mode: both;
-    }
-  }
-
-  .topTitleWhite {
-    display: flex;
-    justify-content: space-between;
-    width: 100%;
-    height: 48px;
-    padding-right: 16px;
-    padding-left: 24px;
-    overflow: hidden;
-    background: #fff;
-    border: 1px solid rgba(0, 0, 0, 10%);
-    border-radius: 8px 8px 0 0;
-
-    .NPC-top-left {
-      display: flex;
-      align-items: center;
-      justify-content: flex-start;
-
-      .NPC-topTitle-text {
-        margin-right: 8px;
-        font-family: 'sourceR';
-        font-size: 16px;
-        font-weight: bold;
-        color: #000;
-      }
-    }
-
-    .NPC-top-right {
-      display: flex;
-      align-items: center;
-      justify-content: flex-start;
-      cursor: pointer;
-
-      &-text {
-        font-size: 14px;
-        font-weight: normal;
-        line-height: 16px;
-        color: #000;
-      }
-
-      img {
-        width: 16px;
-        height: 16px;
-        margin-left: 4px;
-      }
-    }
-
-    img {
-      width: 24px;
-      height: 24px;
-    }
-
-    .rotate {
-      animation-name: firstrotate;
-      animation-timing-function: linear;
-      animation-direction: 2s;
-      animation-fill-mode: both;
-    }
-  }
-
-  .el-collapse-item__content {
-    padding-bottom: 0;
-  }
-
-  .el-slider__button {
-    width: 8px;
-    height: 8px;
-  }
-
-  .el-slider__runway {
-    padding: 0;
-    margin: 0;
-  }
-
-  .el-slider {
-    position: relative;
-
-    // top: -3px;
-  }
-
-  .el-collapse {
-    box-sizing: border-box;
-    background: #f7f7f7;
+    border: 2px solid #346cda;
     border-radius: 8px;
   }
 
-  .el-collapse-item__wrap {
-    background: #f7f7f7;
-    border: 1px solid rgba(0, 0, 0, 10%);
-    border-top: 0;
-    border-radius: 0 0 8px 8px;
-  }
-
-  .el-collapse-item__arrow {
-    display: none;
+  .items-lian {
+    display: block;
+    height: 80px;
+    padding: 0 10px;
+    font-size: 34px;
+    line-height: 80px;
+    color: #346cda;
   }
 
-  .el-table__row {
-    padding: 4px 0;
+  .hanzi-storck {
   }
 }
 </style>

+ 232 - 0
src/views/book/courseware/preview/components/newWord_template/components/Strockplayredline.vue

@@ -0,0 +1,232 @@
+<!--  -->
+<template>
+  <div class="strockplayRedInner">
+    <!-- <div
+      @click="playHanzi"
+      :class="[
+        'strock-play-box',
+        themeColor == 'green'
+          ? 'green-border'
+          : themeColor == 'red'
+          ? 'red-border'
+          : themeColor == 'brown'
+          ? 'brown-border'
+          : 'blue-border',
+      ]"
+      v-if="playStorkes"
+    ></div> -->
+    <template v-if="Book_text != '〇'">
+      <svg-icon
+        icon-class="play-stroke-icon"
+        :className="tianColor ? 'strock-play-red' : 'strock-play-box'"
+        v-if="playStorkes"
+        @click="playHanzi"
+      />
+      <div :id="targetDiv" class="character-target-div" :style="{ padding: '5px' }"></div>
+    </template>
+    <template v-else>
+      <span class="book-text">{{ Book_text }}</span>
+    </template>
+    <svg-icon icon-class="tian" :className="tianColor ? 'tian-bg-red' : 'tian-bg'" />
+  </div>
+</template>
+
+<script>
+import { GetStaticResources } from '@/api/app';
+const HanziWriter = require('hanzi-writer');
+export default {
+  components: {},
+  props: [
+    'targetDiv',
+    'Book_text',
+    'playStorkes',
+    'strokeColor',
+    'wordNum',
+    'themeColor',
+    'tianColor',
+    'curItem',
+    'type',
+    'judgeAnswer',
+    'hz_json',
+  ],
+  data() {
+    return {
+      writer: null,
+    };
+  },
+  computed: {},
+  watch: {
+    targetDiv: {
+      handler: function (val, oldVal) {
+        if (val != oldVal) {
+          let _this = this;
+          _this.$nextTick(() => {
+            _this.initHanziwrite();
+          });
+        }
+      },
+      // 深度观察监听
+      deep: true,
+    },
+  },
+  //方法集合
+  methods: {
+    initHanziwrite() {
+      let _this = this;
+      if (_this.Book_text == '〇') return false;
+      _this.writer = null;
+      //var ren = require("hanzi-writer-data/国");
+      if (_this.curItem) {
+        _this.writer = HanziWriter.default.create(_this.targetDiv, _this.Book_text, {
+          padding: 5,
+          showOutline: true,
+          strokeColor: _this.strokeColor ? (_this.judgeAnswer ? 'rgba(44, 44, 44, 0.45)' : _this.strokeColor) : '#333',
+          radicalColor: _this.judgeAnswer ? '#333' : '#ED5050',
+          charDataLoader: function (char, onComplete) {
+            let charData = _this.handleData(_this.curItem.answer);
+            onComplete(charData);
+          },
+        });
+      } else {
+        _this.writer = HanziWriter.default.create(_this.targetDiv, _this.Book_text, {
+          charDataLoader: function (char, onComplete) {
+            let MethodName = 'hz_resource_manager-GetHZStrokesContent';
+            let data = {
+              hz: char,
+            };
+            GetStaticResources(MethodName, data).then((res) => {
+              onComplete(res);
+            });
+          },
+          padding: 5,
+          showOutline: true,
+          strokeColor: _this.strokeColor ? _this.strokeColor : '#333',
+        });
+      }
+    },
+    handleData(stroke_num) {
+      if (this.hz_json) {
+        let charData = JSON.parse(JSON.stringify(this.hz_json));
+        if (stroke_num) {
+          let stroke_arr = null;
+          if (this.type == 'newWord_template') {
+            stroke_arr = stroke_num.split('、');
+          } else {
+            stroke_arr = stroke_num.split('#');
+          }
+          stroke_arr = stroke_arr.map((item) => (item = Number(item) - 1));
+          charData.radStrokes = stroke_arr;
+        } else if (charData) {
+          charData.radStrokes = [];
+        }
+        return charData;
+      }
+    },
+    playHanzi(event) {
+      let _this = this;
+      _this.writer.animateCharacter();
+      event.stopPropagation();
+    },
+  },
+  //生命周期 - 创建完成(可以访问当前this实例)
+  created() {},
+  //生命周期 - 挂载完成(可以访问DOM元素)
+  mounted() {
+    let _this = this;
+    _this.$nextTick(() => {
+      _this.initHanziwrite();
+    });
+  },
+  beforeCreate() {}, //生命周期 - 创建之前
+  beforeMount() {}, //生命周期 - 挂载之前
+  beforeUpdate() {}, //生命周期 - 更新之前
+  updated() {}, //生命周期 - 更新之后
+  beforeDestroy() {}, //生命周期 - 销毁之前
+  destroyed() {}, //生命周期 - 销毁完成
+  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
+};
+</script>
+<style lang="scss" scoped>
+//@import url(); 引入公共css类
+.strockplayRedInner {
+  position: relative;
+  width: 128px; //444px
+  height: 128px; //480px
+}
+
+.character-target-div {
+  z-index: 99999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  font-family: 'FZJCGFKTK';
+}
+
+.strockplayRedInner {
+  .strock-play-box {
+    position: absolute;
+    top: -2px;
+    right: -2px;
+    z-index: 998;
+
+    // @include font_color("sub_color");
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 22px;
+    height: 22px;
+    color: #346cda;
+    cursor: pointer;
+    background: none;
+  }
+
+  .strock-play-red {
+    position: absolute;
+    top: -2px;
+    right: -2px;
+    z-index: 999;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 22px;
+    height: 22px;
+    color: #d47064;
+    cursor: pointer;
+  }
+
+  .tian-bg {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    stroke: rgba(157, 202, 255, 20%);
+  }
+
+  .tian-bg-red {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    stroke: rgba(212, 112, 100, 20%);
+  }
+}
+
+.animate-butto {
+  width: 240px;
+  height: 160px;
+  font-size: 28px;
+}
+
+.book-text {
+  display: block;
+  width: 100%;
+  height: 100%;
+  font-size: 96px;
+  line-height: 128px;
+  text-align: center;
+}
+</style>

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

@@ -60,7 +60,7 @@ export default {
       _this.writer = null;
       // var ren = require("hanzi-writer-data/国");
       _this.writer = HanziWriter.default.create(_this.targetDiv, _this.Book_text, {
-        charDataLoader (char, onComplete) {
+        charDataLoader(char, onComplete) {
           let MethodName = 'hz_resource_manager-GetHZStrokesContent';
           let data = {
             hz: char,

+ 0 - 1
src/views/book/courseware/preview/components/record_input/RecordInputPreview.vue

@@ -23,7 +23,6 @@
           :many-times="isEnable(data.is_enable_manyTimes)"
           class="record-box"
           :answer-record-list="data.answer.answer_list.answer_record_list"
-          :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
           @handleWav="handleWav"
         />
       </div>

+ 5 - 5
src/views/book/courseware/preview/components/write_base/WriteBasePreview.vue

@@ -23,9 +23,8 @@
       </div>
       <template v-if="data.write_base64[0]">
         <h2>展示区</h2>
-        <img style="background-color: #f7f8fa"
-:src="data.write_base64[0]" alt=""
-      /></template>
+        <img style="background-color: #f7f8fa" :src="data.write_base64[0]" alt="write-show" />
+      </template>
     </div>
   </div>
 </template>
@@ -57,9 +56,10 @@ export default {
   },
   methods: {
     handleReset() {
-      this.$refs.esign.$el.style.backgroundColor = '#f7f8fa';
       this.$refs.esign.reset();
-      this.$refs.esign.$el.style.backgroundColor = '#f7f8fa';
+      this.$nextTick(() => {
+        this.bgColor = '#f7f8fa';
+      });
     },
     handleGenerate() {
       this.$refs.esign

+ 1 - 1
src/views/personal_workbench/check_task/audit/index.vue

@@ -36,7 +36,7 @@
 import MenuPage from '@/views/personal_workbench/common/menu.vue';
 import CommonPreview from '@/components/CommonPreview.vue';
 
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 import { FinishCoursewareCurFlowNodeAudit } from '@/api/project';
 
 export default {

+ 1 - 1
src/views/personal_workbench/common/MenuPopover.vue

@@ -24,7 +24,7 @@
 </template>
 
 <script>
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 
 export default {
   name: 'MenuPopover',

+ 1 - 1
src/views/personal_workbench/edit_task/preview/index.vue

@@ -19,7 +19,7 @@ import MenuPage from '@/views/personal_workbench/common/menu.vue';
 import CommonPreview from '@/components/CommonPreview.vue';
 
 import { SubmitBookCoursewareToAuditFlow } from '@/api/project';
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 
 export default {
   name: 'TaskPreviewPage',

+ 8 - 8
src/views/personal_workbench/project/components/MenuTree.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="menu-list">
     <el-tree highlight-current :data="nodeList" :props="defaultProps" @node-click="handleNodeClick">
-      <div class="custom-tree-node" slot-scope="{ node }">
+      <div slot-scope="{ node }" class="custom-tree-node">
         <div
           :class="[
             'tree-box-item',
@@ -17,7 +17,7 @@
 </template>
 
 <script>
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 
 export default {
   name: 'MenuTree',
@@ -35,12 +35,6 @@ export default {
       default: '',
     },
   },
-  watch: {
-    nodeList: {
-      handler(val) {},
-      immediate: true,
-    },
-  },
   data() {
     return {
       isTrue,
@@ -58,6 +52,12 @@ export default {
       },
     };
   },
+  watch: {
+    nodeList: {
+      handler(val) {},
+      immediate: true,
+    },
+  },
   created() {},
   methods: {
     /**

+ 1 - 1
src/views/project_manage/book/BookPreview.vue

@@ -16,7 +16,7 @@
 import MenuPage from '@/views/personal_workbench/common/menu.vue';
 import CommonPreview from '@/components/CommonPreview.vue';
 
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 
 export default {
   name: 'BookPreview',

+ 1 - 1
src/views/project_manage/book/index.vue

@@ -42,7 +42,7 @@ import PaginationPage from '@/components/PaginationPage.vue';
 import MenuPage from '@/views/personal_workbench/common/menu.vue';
 
 import { PageQueryMyProjectYSJBookList_Leader } from '@/api/list';
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 
 export default {
   name: 'BookPage',

+ 1 - 1
src/views/project_manage/org/book/OrgBookPreview.vue

@@ -22,7 +22,7 @@
 import ProjectMenu from '@/views/project_manage/common/ProjectMenu.vue';
 import CommonPreview from '@/components/CommonPreview.vue';
 
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 
 export default {
   name: 'OrgBookPreview',

+ 1 - 1
src/views/project_manage/org/book/index.vue

@@ -42,7 +42,7 @@ import ProjectMenu from '@/views/project_manage/common/ProjectMenu.vue';
 import PaginationPage from '@/components/PaginationPage.vue';
 
 import { PageQueryYSJBookList_OrgManager } from '@/api/list';
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 
 export default {
   name: 'OrgBookPage',

+ 1 - 1
src/views/project_manage/org/final/OrgFinalPreview.vue

@@ -35,7 +35,7 @@ import ProjectMenu from '@/views/project_manage/common/ProjectMenu.vue';
 import CommonPreview from '@/components/CommonPreview.vue';
 import ConfirmBookInfo from './components/ConfirmBookInfo.vue';
 
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 import { ShangjiaBook, RollbackProject, RejectShangjiaBookRequest, RejectRollbackProjectRequest } from '@/api/project';
 
 export default {

+ 1 - 1
src/views/project_manage/org/project/index.vue

@@ -39,7 +39,7 @@ import PaginationPage from '@/components/PaginationPage.vue';
 import ProjectMenu from '@/views/project_manage/common/ProjectMenu.vue';
 
 import { PageQueryProjectList_OrgManager } from '@/api/list';
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 
 export default {
   name: 'OrgProjectPage',

+ 1 - 1
src/views/project_manage/project/ProjectPreview.vue

@@ -23,7 +23,7 @@ import MenuPage from '@/views/personal_workbench/common/menu.vue';
 import CommonPreview from '@/components/CommonPreview.vue';
 import RequestBook from './components/RequestBook.vue';
 
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 import { RequestShangjiaBook, RequestRollbackProject } from '@/api/project';
 
 export default {

+ 1 - 1
src/views/project_manage/project/index.vue

@@ -43,7 +43,7 @@ import PaginationPage from '@/components/PaginationPage.vue';
 import MenuPage from '@/views/personal_workbench/common/menu.vue';
 
 import { PageQueryMyProjectList_Leader } from '@/api/list';
-import { isTrue } from '@/utils/common';
+import { isTrue } from '@/utils/validate';
 
 export default {
   name: 'ProjectManage',