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

Merge branch 'master' of http://gcls-git.helxsoft.cn/dsy/EEP_BookPreview

zq 3 месяцев назад
Родитель
Сommit
5f2464ede7
44 измененных файлов с 2152 добавлено и 250 удалено
  1. BIN
      public/favicon.ico
  2. 8 0
      src/api/article.js
  3. BIN
      src/assets/icon/arrow-down.png
  4. BIN
      src/assets/icon/arrow-up.png
  5. BIN
      src/assets/icon/sidebar-audio.png
  6. BIN
      src/assets/icon/sidebar-audit.png
  7. BIN
      src/assets/icon/sidebar-collect.png
  8. BIN
      src/assets/icon/sidebar-file.png
  9. BIN
      src/assets/icon/sidebar-fullscreen.png
  10. BIN
      src/assets/icon/sidebar-image.png
  11. BIN
      src/assets/icon/sidebar-knowledge.png
  12. BIN
      src/assets/icon/sidebar-mindmap.png
  13. BIN
      src/assets/icon/sidebar-note.png
  14. BIN
      src/assets/icon/sidebar-search.png
  15. BIN
      src/assets/icon/sidebar-setting.png
  16. BIN
      src/assets/icon/sidebar-toolkit.png
  17. BIN
      src/assets/icon/sidebar-totalResources.png
  18. BIN
      src/assets/icon/sidebar-translate.png
  19. BIN
      src/assets/icon/sidebar-video.png
  20. 0 2
      src/components/RichText.vue
  21. 0 1
      src/styles/mixin.scss
  22. 1 1
      src/styles/variables.scss
  23. 109 16
      src/views/book/courseware/preview/CoursewarePreview.vue
  24. 2 1
      src/views/book/courseware/preview/common/SoundRecord.vue
  25. 18 10
      src/views/book/courseware/preview/components/article/NormalModelChs.vue
  26. 18 11
      src/views/book/courseware/preview/components/article/PhraseModelChs.vue
  27. 18 10
      src/views/book/courseware/preview/components/article/Practicechs.vue
  28. 18 11
      src/views/book/courseware/preview/components/article/WordModelChs.vue
  29. 22 7
      src/views/book/courseware/preview/components/article/components/Notecard.vue
  30. 102 30
      src/views/book/courseware/preview/components/article/components/WordPhraseDetail.vue
  31. 0 1
      src/views/book/courseware/preview/components/article/components/Wordcard.vue
  32. 28 16
      src/views/book/courseware/preview/components/article/index.vue
  33. 7 2
      src/views/book/courseware/preview/components/dialogue_article/NormalModelChs.vue
  34. 7 2
      src/views/book/courseware/preview/components/dialogue_article/PhraseModelChs.vue
  35. 6 1
      src/views/book/courseware/preview/components/dialogue_article/Practicechs.vue
  36. 7 2
      src/views/book/courseware/preview/components/dialogue_article/WordModelChs.vue
  37. 28 16
      src/views/book/courseware/preview/components/dialogue_article/index.vue
  38. 7 0
      src/views/book/courseware/preview/components/h5_games/H5GamesPreview.vue
  39. 205 47
      src/views/book/courseware/preview/components/new_word/NewWordPreview.vue
  40. 1 1
      src/views/book/courseware/preview/components/new_word/components/AudioPlay.vue
  41. 82 22
      src/views/book/courseware/preview/components/new_word/components/WordPhraseDetail.vue
  42. 880 0
      src/views/book/courseware/preview/components/new_word/components/writeTableZoom.vue
  43. 11 13
      src/views/book/courseware/preview/components/notes/NotesPreview.vue
  44. 567 27
      src/web_preview/index.vue

BIN
public/favicon.ico


+ 8 - 0
src/api/article.js

@@ -29,3 +29,11 @@ export function analysSubmit(data) {
 export function fileToBase64Text(data) {
   return http.post(`/FileServer/SI?MethodName=file_store_manager-GetFileByteBase64Text`, data, {}, true);
 }
+
+// 检索课件单词的例句列表
+export function getCoursewareWordExampleSentenceList(data) {
+  return http.post(
+    `${process.env.VUE_APP_EepServer}?MethodName=book_content_manager-GetCoursewareWordExampleSentenceList_Sort`,
+    data,
+  );
+}

BIN
src/assets/icon/arrow-down.png


BIN
src/assets/icon/arrow-up.png


BIN
src/assets/icon/sidebar-audio.png


BIN
src/assets/icon/sidebar-audit.png


BIN
src/assets/icon/sidebar-collect.png


BIN
src/assets/icon/sidebar-file.png


BIN
src/assets/icon/sidebar-fullscreen.png


BIN
src/assets/icon/sidebar-image.png


BIN
src/assets/icon/sidebar-knowledge.png


BIN
src/assets/icon/sidebar-mindmap.png


BIN
src/assets/icon/sidebar-note.png


BIN
src/assets/icon/sidebar-search.png


BIN
src/assets/icon/sidebar-setting.png


BIN
src/assets/icon/sidebar-toolkit.png


BIN
src/assets/icon/sidebar-totalResources.png


BIN
src/assets/icon/sidebar-translate.png


BIN
src/assets/icon/sidebar-video.png


+ 0 - 2
src/components/RichText.vue

@@ -65,7 +65,6 @@ export default {
     Editor,
     MathDialog,
   },
-  inject: ['getBookUnifiedAttr'],
   inheritAttrs: false,
   props: {
     inline: {
@@ -142,7 +141,6 @@ export default {
         left: 0,
       },
       id: getRandomNumber(),
-      bookUnifiedAttr: this.getBookUnifiedAttr(),
       init: {
         content_style: `
           mjx-container, mjx-container * {

+ 0 - 1
src/styles/mixin.scss

@@ -41,7 +41,6 @@
 @mixin preview-base {
   display: grid;
   gap: 6px;
-  padding: $border-component-spacing;
 
   :deep .rich-text {
     @include rich-text;

+ 1 - 1
src/styles/variables.scss

@@ -19,6 +19,7 @@ $right-color: #30a47d;
 $right-bc-color: #e8f7f2;
 $label-color: #076aff;
 $setting-active-color: #4176ff;
+$courseware-bgColor: #fff; // 教材内容背景色
 
 // px
 $header-h: 64px; // 顶部内容高度
@@ -29,6 +30,5 @@ $courseware-top-padding: 65px; // 教材顶部页边距
 $courseware-bottom-padding: 65px; // 教材底部页边距
 $component-spacing: 24px; // 组件间间距
 $title-content-spacing: 65px; // 标题与内容间距
-$border-component-spacing: 10px; // 组件边框与组件内容间距
 $catalogue-width: 300px; // 目录宽度
 $sidebar-width: 300px; // 工具栏宽度

+ 109 - 16
src/views/book/courseware/preview/CoursewarePreview.vue

@@ -1,19 +1,5 @@
 <template>
-  <div
-    ref="courserware"
-    class="courserware"
-    :style="[
-      {
-        backgroundImage: background.background_image_url ? `url(${background.background_image_url})` : '',
-        backgroundSize: background.background_image_url
-          ? `${background.background_position.width}% ${background.background_position.height}%`
-          : '',
-        backgroundPosition: background.background_image_url
-          ? `${background.background_position.left}% ${background.background_position.top}%`
-          : '',
-      },
-    ]"
-  >
+  <div ref="courserware" class="courserware" :style="computedCourserwareStyle()">
     <template v-for="(row, i) in data.row_list">
       <div v-show="computedRowVisibility(row.row_id)" :key="i" class="row" :style="getMultipleColStyle(i)">
         <el-checkbox
@@ -31,6 +17,7 @@
             <template v-for="(grid, k) in col.grid_list">
               <component
                 :is="previewComponentList[grid.type]"
+                :id="grid.id"
                 :key="k"
                 ref="preview"
                 :content="computedColContent(grid.id)"
@@ -40,7 +27,41 @@
                   gridArea: grid.grid_area,
                   height: grid.height,
                 }"
+                @contextmenu.native.prevent="handleContextMenu($event, grid.id)"
               />
+
+              <div
+                v-if="showMenu && componentId === grid.id"
+                :key="'menu' + grid.id + k"
+                class="custom-context-menu"
+                :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }"
+                @click="handleMenuItemClick"
+              >
+                添加批注
+              </div>
+              <div
+                v-if="showRemark && Object.keys(componentRemarkObj).length !== 0 && componentRemarkObj[grid.id]"
+                :key="'show' + grid.id + k"
+              >
+                <el-popover
+                  v-for="(items, indexs) in componentRemarkObj[grid.id]"
+                  :key="indexs"
+                  placement="bottom"
+                  width="200"
+                  trigger="click"
+                >
+                  <div v-html="items.content"></div>
+                  <template #reference>
+                    <SvgIcon
+                      slot="reference"
+                      icon-class="icon-info"
+                      size="24"
+                      class="remark-info"
+                      :style="{ left: items.position_x - 12 + 'px', top: items.position_y - 12 + 'px' }"
+                    />
+                  </template>
+                </el-popover>
+              </div>
             </template>
           </div>
         </template>
@@ -97,6 +118,10 @@ export default {
       type: Boolean,
       default: true,
     },
+    project: {
+      type: Object,
+      default: () => ({}),
+    },
   },
   data() {
     return {
@@ -134,6 +159,10 @@ export default {
       left: rect.left,
       top: rect.top,
     };
+    window.addEventListener('mousedown', this.handleMouseDown);
+  },
+  beforeDestroy() {
+    window.removeEventListener('mousedown', this.handleMouseDown);
   },
   methods: {
     /**
@@ -281,6 +310,70 @@ export default {
         gridTemplateRows,
       };
     },
+
+    /**
+     * 计算课件背景样式
+     * @returns {Object} 课件背景样式对象
+     */
+    computedCourserwareStyle() {
+      const { background_image_url: bcImgUrl = '', background_position: pos = {} } = this.background || {};
+      const hasNoRows = !Array.isArray(this.data?.row_list) || this.data.row_list.length === 0;
+      const projectCover = this.project?.cover_image_file_url || '';
+
+      // 优先在空行时使用背景图或项目封面
+      const backgroundImage = hasNoRows ? bcImgUrl || projectCover : '';
+
+      // 保护性读取位置/大小值,避免 undefined 导致字符串 "undefined%"
+      const widthPct = typeof pos.width === 'undefined' ? '' : pos.width;
+      const heightPct = typeof pos.height === 'undefined' ? '' : pos.height;
+      const leftPct = typeof pos.left === 'undefined' ? '' : pos.left;
+      const topPct = typeof pos.top === 'undefined' ? '' : pos.top;
+
+      const hasBcImg = Boolean(bcImgUrl);
+      const backgroundSize = hasBcImg ? `${widthPct}% ${heightPct}%` : hasNoRows ? 'contain' : '';
+      const backgroundPosition = hasBcImg ? `${leftPct}% ${topPct}%` : hasNoRows ? 'center' : '';
+
+      return {
+        backgroundImage: backgroundImage ? `url(${backgroundImage})` : '',
+        backgroundSize,
+        backgroundPosition,
+      };
+    },
+    handleContextMenu(event, id) {
+      if (this.canRemark) {
+        event.preventDefault(); // 阻止默认的上下文菜单显示
+        this.menuPosition = {
+          x: event.clientX - this.divPosition.left,
+          y: event.clientY - this.divPosition.top,
+        }; // 设置菜单位置
+        this.componentId = id;
+        this.$emit('computeScroll');
+      }
+    },
+    handleResult(top, left, select_node) {
+      this.menuPosition = {
+        x: this.menuPosition.x + left,
+        y: this.menuPosition.y + top,
+        select_node,
+      }; // 设置菜单位置
+      this.showMenu = true; // 显示菜单
+    },
+    handleMenuItemClick() {
+      this.showMenu = false; // 隐藏菜单
+      this.$emit(
+        'addRemark',
+        this.menuPosition.select_node,
+        this.menuPosition.x,
+        this.menuPosition.y,
+        this.componentId,
+      );
+    },
+    handleMouseDown(event) {
+      if (event.button === 0 && event.target.className !== 'custom-context-menu') {
+        // 0 表示左键
+        this.showMenu = false;
+      }
+    },
     /**
      * 查找子组件
      * @param {string} id 组件的唯一标识符
@@ -330,7 +423,7 @@ export default {
     height: 15px;
     pointer-events: none;
     content: '';
-    background: #f9f9f9;
+    background: $courseware-bgColor;
   }
 
   &::before {

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

@@ -519,7 +519,8 @@ export default {
     width: 24px;
     height: 24px;
     margin-left: 8px;
-    color: #000;
+    color: rgba(0, 0, 0, 30%);
+    cursor: pointer;
 
     // background: url('@/assets/voice_matrix/luyin-delete.png') center no-repeat;
     // background-size: 100%;

+ 18 - 10
src/views/book/courseware/preview/components/article/NormalModelChs.vue

@@ -1106,19 +1106,26 @@
         </div>
       </template>
     </template>
-    <div v-for="(items, indexs) in curQue.detail" :key="indexs" class="multilingual">
+    <template v-for="(items, indexs) in curQue.detail">
       <div
-        v-if="curQue.property.multilingual_position === 'all'"
-        class="multilingual-para"
-        :class="[items.isTitle ? 'multilingual-para-center' : '']"
+        v-if="
+          curQue.property.multilingual_position === 'all' &&
+          items.multilingualTextList &&
+          items.multilingualTextList[multilingual] &&
+          items.multilingualTextList[multilingual].length > 0
+        "
+        :key="indexs"
+        class="multilingual"
       >
-        {{
-          items.multilingualTextList && items.multilingualTextList[multilingual]
-            ? items.multilingualTextList[multilingual].join(' ')
-            : ''
-        }}
+        <div class="multilingual-para" :class="[items.isTitle ? 'multilingual-para-center' : '']">
+          {{
+            items.multilingualTextList && items.multilingualTextList[multilingual]
+              ? items.multilingualTextList[multilingual].join(' ')
+              : ''
+          }}
+        </div>
       </div>
-    </div>
+    </template>
     <div
       v-if="
         ((curQue.mp3_list && curQue.mp3_list.length > 0 && curQue.mp3_list[0].url) ||
@@ -1806,6 +1813,7 @@ export default {
     max-width: 100%;
     margin-top: -196px;
     overflow: auto;
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 15%);
   }
 
   .NNPE-detail-box {

+ 18 - 11
src/views/book/courseware/preview/components/article/PhraseModelChs.vue

@@ -731,19 +731,26 @@
         </div>
       </template>
     </template>
-    <div v-for="(items, indexs) in curQue.detail" :key="indexs" class="multilingual">
+    <template v-for="(items, indexs) in curQue.detail">
       <div
-        v-if="curQue.property.multilingual_position === 'all'"
-        class="multilingual-para"
-        :class="[items.isTitle ? 'multilingual-para-center' : '']"
+        v-if="
+          curQue.property.multilingual_position === 'all' &&
+          items.multilingualTextList &&
+          items.multilingualTextList[multilingual] &&
+          items.multilingualTextList[multilingual].length > 0
+        "
+        :key="indexs"
+        class="multilingual"
       >
-        {{
-          items.multilingualTextList && items.multilingualTextList[multilingual]
-            ? items.multilingualTextList[multilingual].join(' ')
-            : ''
-        }}
+        <div class="multilingual-para" :class="[items.isTitle ? 'multilingual-para-center' : '']">
+          {{
+            items.multilingualTextList && items.multilingualTextList[multilingual]
+              ? items.multilingualTextList[multilingual].join(' ')
+              : ''
+          }}
+        </div>
       </div>
-    </div>
+    </template>
     <div
       v-if="
         ((curQue.mp3_list && curQue.mp3_list.length > 0 && curQue.mp3_list[0].url) ||
@@ -1342,7 +1349,7 @@ export default {
       if (answer) {
         let writeModel = this.curQue.Bookanswer.writeModel;
         let hz = answer.hz;
-        if (writeModel.hasOwn(hz)) {
+        if (writeModel.hasOwnProperty(hz)) {
           writeModel[hz].push(answer);
         } else {
           writeModel[hz] = [answer];

+ 18 - 10
src/views/book/courseware/preview/components/article/Practicechs.vue

@@ -480,6 +480,7 @@
                 @handleParentPlay="handleParentPlay"
                 @sentPause="sentPause"
                 @handleWav="handleWav"
+                :attrib="attrib"
               />
               <div v-if="curQue.mp3_list && curQue.mp3_list.length > 0" class="compare-box">
                 <Audio-compare
@@ -515,19 +516,26 @@
         </div> -->
       </div>
     </template>
-    <div v-for="(items, indexs) in curQue.detail" :key="indexs" class="multilingual">
+    <template v-for="(items, indexs) in curQue.detail">
       <div
-        v-if="curQue.property.multilingual_position === 'all'"
-        class="multilingual-para"
-        :class="[items.isTitle ? 'multilingual-para-center' : '']"
+        v-if="
+          curQue.property.multilingual_position === 'all' &&
+          items.multilingualTextList &&
+          items.multilingualTextList[multilingual] &&
+          items.multilingualTextList[multilingual].length > 0
+        "
+        :key="indexs"
+        class="multilingual"
       >
-        {{
-          items.multilingualTextList && items.multilingualTextList[multilingual]
-            ? items.multilingualTextList[multilingual].join(' ')
-            : ''
-        }}
+        <div class="multilingual-para" :class="[items.isTitle ? 'multilingual-para-center' : '']">
+          {{
+            items.multilingualTextList && items.multilingualTextList[multilingual]
+              ? items.multilingualTextList[multilingual].join(' ')
+              : ''
+          }}
+        </div>
       </div>
-    </div>
+    </template>
     <div
       v-if="
         ((curQue.mp3_list && curQue.mp3_list.length > 0 && curQue.mp3_list[0].url) ||

+ 18 - 11
src/views/book/courseware/preview/components/article/WordModelChs.vue

@@ -727,19 +727,26 @@
         </div>
       </template>
     </template>
-    <div v-for="(items, indexs) in curQue.detail" :key="indexs" class="multilingual">
+    <template v-for="(items, indexs) in curQue.detail">
       <div
-        v-if="curQue.property.multilingual_position === 'all'"
-        class="multilingual-para"
-        :class="[items.isTitle ? 'multilingual-para-center' : '']"
+        v-if="
+          curQue.property.multilingual_position === 'all' &&
+          items.multilingualTextList &&
+          items.multilingualTextList[multilingual] &&
+          items.multilingualTextList[multilingual].length > 0
+        "
+        :key="indexs"
+        class="multilingual"
       >
-        {{
-          items.multilingualTextList && items.multilingualTextList[multilingual]
-            ? items.multilingualTextList[multilingual].join(' ')
-            : ''
-        }}
+        <div class="multilingual-para" :class="[items.isTitle ? 'multilingual-para-center' : '']">
+          {{
+            items.multilingualTextList && items.multilingualTextList[multilingual]
+              ? items.multilingualTextList[multilingual].join(' ')
+              : ''
+          }}
+        </div>
       </div>
-    </div>
+    </template>
     <div
       v-if="
         ((curQue.mp3_list && curQue.mp3_list.length > 0 && curQue.mp3_list[0].url) ||
@@ -1215,7 +1222,7 @@ export default {
       if (answer) {
         let writeModel = this.curQue.Bookanswer.writeModel;
         let hz = answer.hz;
-        if (writeModel.hasOwn(hz)) {
+        if (writeModel.hasOwnProperty(hz)) {
           writeModel[hz].push(answer);
         } else {
           writeModel[hz] = [answer];

+ 22 - 7
src/views/book/courseware/preview/components/article/components/Notecard.vue

@@ -6,12 +6,23 @@
     </div>
     <div class="NPC-notes">
       <div class="NPC-notes-con">
-        <span class="NPC-notes-con-number">{{ item.number }}</span>
-        <span class="NPC-notes-con-text">{{ item.con }}</span>
-      </div>
-      <div class="NPC-notes-trans">
-        {{ item.interpret }}
+        <span
+          class="NPC-notes-con-number"
+          :style="{
+            color: attrib && attrib.topic_color ? attrib.topic_color : '',
+          }"
+          >{{ item.number }}</span
+        >
+        <span
+          class="NPC-notes-con-text"
+          v-html="item.con"
+          :style="{
+            color: attrib && attrib.topic_color ? attrib.topic_color : '',
+            fontSize: attrib && attrib.font_size ? attrib.font_size : '',
+          }"
+        ></span>
       </div>
+      <div class="NPC-notes-trans" v-html="item.interpret"></div>
       <div v-if="item.note" class="NPC-notes-note" v-html="item.note"></div>
       <div v-if="item.img_list && item.img_list.length > 0" class="NPC-notes-note-img">
         <div v-for="(imgItem, imgIndex) in item.img_list" :key="'imgList' + imgIndex">
@@ -25,7 +36,7 @@
 <script>
 export default {
   components: {},
-  props: ['item', 'changeCard'],
+  props: ['item', 'changeCard', 'attrib'],
   data() {
     return {};
   },
@@ -54,7 +65,7 @@ export default {
   max-height: 261px;
   padding: 16px;
   overflow-y: auto;
-  background: #fff;
+  background: rgb(236, 238, 243);
   border-radius: 8px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 15%);
 
@@ -134,5 +145,9 @@ export default {
       }
     }
   }
+
+  :deep p {
+    margin: 0;
+  }
 }
 </style>

+ 102 - 30
src/views/book/courseware/preview/components/article/components/WordPhraseDetail.vue

@@ -29,6 +29,9 @@
               'bwc-Strockplay',
               themeColor == 'green' ? 'green-border' : themeColor == 'red' ? 'red-border' : 'brown-border',
             ]"
+            :style="{
+              borderColor: attrib && attrib.topic_color ? attrib.topic_color : '',
+            }"
           >
             <div v-for="(conItem, conindex) in data.new_word_str" :key="'new_word_' + conindex" class="strockplay">
               <Strockplayredline
@@ -38,10 +41,14 @@
                 :target-div="'bwcHanziIntp' + conItem + detailIndex + conindex"
                 :word-num="data.new_word_str.length"
                 :theme-color="themeColor"
+                :attrib="attrib"
               />
               <div
                 v-if="conindex < data.new_word_str.length - 1"
                 :class="['bwc-line', themeColor == 'green' ? 'green-bg' : themeColor == 'red' ? 'red-bg' : 'brown-bg']"
+                :style="{
+                  background: attrib && attrib.topic_color ? attrib.topic_color : '',
+                }"
               ></div>
             </div>
           </div>
@@ -51,6 +58,9 @@
               'bwc-tolength',
               themeColor == 'green' ? 'green-border' : themeColor == 'red' ? 'red-border' : 'brown-border',
             ]"
+            :style="{
+              borderColor: attrib && attrib.topic_color ? attrib.topic_color : '',
+            }"
           >
             <span v-for="(item, index) in data.new_word_str" :key="index">{{ item }}</span>
           </p>
@@ -135,11 +145,66 @@
                 </div>
               </div> -->
             </div>
+            <div :class="['liju', lijuPatternIndex == 1 ? 'KWIC_liju' : '']">
+              <div v-if="lijuPatternIndex == 1" class="sort_dv">
+                <!-- <span :class="['sort', sortIndex == 0 ? 'sele' : '']" @click="sortEvent(0, 'sentence_list_sort_left')">
+                  <img src="../../../../assets/NPC/analys-right.png" alt="" />
+                </span>
+                <span :class="['sort', sortIndex == 1 ? 'sele' : '']" @click="sortEvent(1, 'sentence_list_sort_mid')">
+                  <img src="../../../../assets/NPC/analys-center.png" alt="" />
+                </span>
+                <span :class="['sort', sortIndex == 2 ? 'sele' : '']" @click="sortEvent(2, 'sentence_list_sort_right')">
+                  <img src="../../../../assets/NPC/analys-left.png" alt="" />
+                </span>
+                <span class="down" @click="downloadImage">
+                  <img src="../../../../assets/NPC/download.png" alt="" />
+                </span> -->
+              </div>
+              <div class="liju_main">
+                <div v-for="(item, i) in CurrentList" :key="i">
+                  <div class="number">{{ i + 1 }}.</div>
+                  <div>
+                    <template v-if="lijuPatternIndex == 1">
+                      <el-tooltip effect="dark" placement="bottom">
+                        <div slot="content">
+                          {{ item.source_courseware_name_path }}
+                        </div>
+                        <p class="p1">
+                          {{ item.show_source_courseware_name_path }}
+                        </p>
+                      </el-tooltip>
+                      <div class="p2">
+                        <div v-for="(txt, indexs) in item.resArr" :key="indexs">
+                          <span v-for="(txts, indexs) in txt" :key="indexs" v-html="txts"></span>
+                        </div>
+                      </div>
+                      <!-- <p class="p2" v-html="item.resArr"></p> -->
+                    </template>
+                    <template v-else>
+                      <p v-html="item.res"></p>
+                      <p class="p2">
+                        {{ item.source_courseware_name_path }}
+                      </p>
+                    </template>
+                  </div>
+                </div>
+                <p
+                  v-if="CurrentList.length === 0"
+                  :style="{
+                    textAlign: 'center',
+                    margin: '0',
+                    padding: ' 16px 0 0 24px',
+                  }"
+                >
+                  暂无例句
+                </p>
+              </div>
+            </div>
           </div>
-          <!-- <div v-if="list1 && list1.length > 0" v-loading="loading1">
+          <div v-if="list1 && list1.length > 0" v-loading="loading1">
             <div class="topTitle">
               <span>本课例句({{ list1.length }})</span>
-              <span @click="handleChangeTab('wordShow')"
+              <!-- <span @click="handleChangeTab('wordShow')"
                 >{{ wordShow ? "收起" : "展开" }}
                 <img
                   v-if="wordShow"
@@ -147,7 +212,7 @@
                   alt=""
                 />
                 <img v-else src="../../../../assets/NPC/up-black.png" alt="" />
-              </span>
+              </span> -->
             </div>
             <el-collapse-transition>
               <div class="liju" v-show="wordShow">
@@ -166,7 +231,7 @@
           <div v-if="list2 && list2.length > 0" v-loading="loading2">
             <div class="topTitle">
               <span>本书例句({{ list2.length }})</span>
-              <span @click="handleChangeTab('wordShow2')"
+              <!-- <span @click="handleChangeTab('wordShow2')"
                 >{{ wordShow2 ? "收起" : "展开" }}
                 <img
                   v-if="wordShow2"
@@ -174,7 +239,7 @@
                   alt=""
                 />
                 <img v-else src="../../../../assets/NPC/up-black.png" alt="" />
-              </span>
+              </span> -->
             </div>
             <el-collapse-transition>
               <div class="liju" v-show="wordShow2">
@@ -193,14 +258,14 @@
           <div v-if="list3 && list3.length > 0" v-loading="loading3">
             <div class="topTitle">
               <span>本套教材例句({{ list3.length }})</span>
-              <span @click="handleChangeTab('wordShow3')"
+              <!-- <span @click="handleChangeTab('wordShow3')"
                 >{{ wordShow3 ? "收起" : "展开" }}
                 <img
                   v-if="wordShow3"
                   src="../../../../assets/NPC/down-black.png"
                 />
                 <img v-else src="../../../../assets/NPC/up-black.png" alt="" />
-              </span>
+              </span> -->
             </div>
             <el-collapse-transition>
               <div class="liju" v-if="wordShow3">
@@ -215,7 +280,7 @@
                 </div>
               </div>
             </el-collapse-transition>
-          </div> -->
+          </div>
         </div>
       </div>
     </div>
@@ -226,7 +291,8 @@
 <script>
 import Strockplayredline from './Strockplayredline.vue';
 import Audio from './AudioRed.vue';
-import { GetBookWebSIContent, GetStaticResources } from '@/api/app';
+import { GetStaticResources } from '@/api/app';
+import { getCoursewareWordExampleSentenceList } from '@/api/article';
 
 export default {
   // import引入的组件需要注入到对象中才能使用
@@ -234,6 +300,7 @@ export default {
     Strockplayredline,
     Audio,
   },
+  inject: ['getSelectId'],
   props: [
     'data',
     'changeDetailIndex',
@@ -248,6 +315,7 @@ export default {
     'type',
     'bg',
     'ed',
+    'attrib',
   ],
   data() {
     // 这里存放数据
@@ -408,20 +476,19 @@ export default {
       this.loading1 = true;
       this.loading2 = true;
       this.loading3 = true;
-      let Mname = 'book-courseware_manager-GetCoursewareWordExampleSentenceList_Sort';
+      let currentTreeID = this.$route.params.id || this.getSelectId();
       // 获取本课的 本教材的 本套的 的例句
-      GetBookWebSIContent(Mname, {
-        courseware_id: this.currentTreeID, // 课件id
+      getCoursewareWordExampleSentenceList({
+        courseware_id: currentTreeID, // 课件id
         word: this.data.new_word, // 生词
         search_scope: 0, // 检索范围0 本课件  1本教材 2本套
-        is_contain_word_variants: false,
+        is_contain_word_variants: 'false',
         is_filter_repetitive_sentence: 'true',
-        book_publish_status: 1,
+        book_publish_status: -1,
         sort_mode: 'ASCENT',
         compare_mode: 'SOURCE',
         book_id_list: [],
-        is_contain_word_variants: false,
-        is_contain_stat_data: false,
+        is_contain_stat_data: 'false',
         sentence_fc_length_min: -1,
         sentence_fc_length_max: -1,
       })
@@ -461,18 +528,17 @@ export default {
             //   this.list1.sentence_list_sort_right
             // );
           }
-          GetBookWebSIContent(Mname, {
-            courseware_id: this.currentTreeID, // 课件id
+          getCoursewareWordExampleSentenceList({
+            courseware_id: currentTreeID, // 课件id
             word: this.data.new_word, // 生词
             search_scope: 1, // 检索范围0 本课件  1本教材 2本套
-            is_contain_word_variants: false,
+            is_contain_word_variants: 'false',
             is_filter_repetitive_sentence: 'true',
-            book_publish_status: 1,
+            book_publish_status: -1,
             sort_mode: 'ASCENT',
             compare_mode: 'SOURCE',
             book_id_list: [],
-            is_contain_word_variants: false,
-            is_contain_stat_data: false,
+            is_contain_stat_data: 'false',
             sentence_fc_length_min: -1,
             sentence_fc_length_max: -1,
           })
@@ -504,19 +570,17 @@ export default {
                 //   this.list2.sentence_list_sort_right
                 // );
               }
-
-              GetBookWebSIContent(Mname, {
-                courseware_id: this.currentTreeID, // 课件id
+              getCoursewareWordExampleSentenceList({
+                courseware_id: currentTreeID, // 课件id
                 word: this.data.new_word, // 生词
                 search_scope: 2, // 检索范围0 本课件  1本教材 2本套
-                is_contain_word_variants: false,
+                is_contain_word_variants: 'false',
                 is_filter_repetitive_sentence: 'true',
-                book_publish_status: 1,
+                book_publish_status: -1,
                 sort_mode: 'ASCENT',
                 compare_mode: 'SOURCE',
                 book_id_list: [],
-                is_contain_word_variants: false,
-                is_contain_stat_data: false,
+                is_contain_stat_data: 'false',
                 sentence_fc_length_min: -1,
                 sentence_fc_length_max: -1,
               })
@@ -548,7 +612,15 @@ export default {
                     //   this.list3.sentence_list_sort_right
                     // );
                   }
-                  this.CurrentList = JSON.parse(JSON.stringify(this.allList.sentence_list));
+                  this.CurrentList = JSON.parse(
+                    JSON.stringify(
+                      this.list1.sentence_list.length !== 0
+                        ? this.list1.sentence_list
+                        : this.list2.sentence_list.length !== 0
+                          ? this.list2.sentence_list
+                          : this.list3.sentence_list,
+                    ),
+                  );
                   this.loading3 = false;
                 })
                 .catch((err) => {

+ 0 - 1
src/views/book/courseware/preview/components/article/components/Wordcard.vue

@@ -463,7 +463,6 @@ export default {
         // _this.mp3Url = _this.word.detail.mp3_list[0].id;
       }
       this.word.detail.mp3Url = this.mp3Url;
-      console.log(this.word.detail);
       if (_this.word.detail.definition_list && _this.word.detail.definition_list.length > 0) {
         // _this.word.detail.definition_list.forEach((item) => {
         if (/^[\u4E00-\u9FA5]+$/.test(_this.word.detail.definition_list)) {

+ 28 - 16
src/views/book/courseware/preview/components/article/index.vue

@@ -77,8 +77,8 @@
                     ? data.unified_attrib.topic_color
                     : '',
               }"
-              ><svg-icon icon-class="icon-article-ci" size="24" />本课生词</a
-            >
+              ><svg-icon icon-class="icon-article-ci" size="24"
+            /></a>
             <a
               v-if="data.property.is_enable_read === 'true'"
               @click="handleSwitchChange('showPhrases', 'showWord', 'showPractice')"
@@ -88,8 +88,8 @@
                     ? data.unified_attrib.topic_color
                     : '',
               }"
-              ><svg-icon icon-class="icon-article-practice" size="24" />语音练习</a
-            >
+              ><svg-icon icon-class="icon-article-practice" size="24"
+            /></a>
             <a
               v-if="data.property.is_enable_word === 'true'"
               @click="handleSwitchChange('showPhrases', 'showPractice', 'showWord')"
@@ -99,8 +99,8 @@
                     ? data.unified_attrib.topic_color
                     : '',
               }"
-              ><svg-icon icon-class="icon-article-phrase" size="24" />取词</a
-            >
+              ><svg-icon icon-class="icon-article-phrase" size="24"
+            /></a>
           </div>
           <!-- <div class="setting-fontsize">
         <a @click="handleFontsize('-')"
@@ -209,13 +209,19 @@
           />
         </div>
       </div>
-      <template v-if="data.new_word_list.new_word_list.length > 0">
+      <template
+        v-if="data.new_word_list && data.new_word_list.new_word_list && data.new_word_list.new_word_list.length > 0"
+      >
         <NewWordPreview :new-data="data.new_word_list" />
       </template>
-      <template v-if="data.other_word_list.new_word_list.length > 0">
+      <template
+        v-if="
+          data.other_word_list && data.other_word_list.new_word_list && data.other_word_list.new_word_list.length > 0
+        "
+      >
         <NewWordPreview :new-data="data.other_word_list" />
       </template>
-      <template v-if="data.notes_list.option.length > 0">
+      <template v-if="data.notes_list && data.notes_list.option && data.notes_list.option.length > 0">
         <NotesPreview :notes-data="data.notes_list" />
       </template>
     </div>
@@ -299,10 +305,10 @@ export default {
       handler(val) {
         if (val) {
           this.data.multilingual.forEach((item) => {
-            let trans_arr = item.translation.split('\n');
+            let trans_arr = item.translation ? item.translation.split('\n') : [];
             this.data.detail.forEach((items) => {
               let items_trans_arr = [];
-              if (!items.hasOwn('multilingualTextList')) {
+              if (!items.hasOwnProperty('multilingualTextList')) {
                 this.$set(items, 'multilingualTextList', {});
               }
               if (items.para) {
@@ -479,10 +485,10 @@ export default {
 
       if (this.showLang) {
         this.data.multilingual.forEach((item) => {
-          let trans_arr = item.translation.split('\n');
+          let trans_arr = item.translation ? item.translation.split('\n') : [];
           this.data.detail.forEach((items) => {
             let items_trans_arr = [];
-            if (!items.hasOwn('multilingualTextList')) {
+            if (!items.hasOwnProperty('multilingualTextList')) {
               this.$set(items, 'multilingualTextList', {});
             }
             if (items.para) {
@@ -500,9 +506,15 @@ export default {
       this.NNPENewWordList = (
         this.data.new_word_list_other_component_input ? this.data.new_word_list_other_component_input : []
       )
-        .concat(this.data.new_word_list.new_word_list)
-        .concat(this.data.other_word_list.new_word_list);
-      this.NNPEAnnotationList = this.data.notes_list.option;
+        .concat(
+          this.data.new_word_list && this.data.new_word_list.new_word_list ? this.data.new_word_list.new_word_list : [],
+        )
+        .concat(
+          this.data.other_word_list && this.data.other_word_list.new_word_list
+            ? this.data.other_word_list.new_word_list
+            : [],
+        );
+      this.NNPEAnnotationList = this.data.notes_list && this.data.notes_list.option ? this.data.notes_list.option : [];
       let resArr = [];
       let sentArrTotal = [];
       let timeArr = [];

+ 7 - 2
src/views/book/courseware/preview/components/dialogue_article/NormalModelChs.vue

@@ -591,10 +591,14 @@
           <div :class="['empty-left', isHasRemark ? 'hasRemark' : '']"></div>
           <div class="empty-right"></div>
         </div>
-
         <template v-for="(items, indexs) in curQue.detail">
           <div
-            v-if="curQue.property.multilingual_position === 'all' && items.multilingualTextList[multilingual]"
+            v-if="
+              curQue.property.multilingual_position === 'all' &&
+              items.multilingualTextList &&
+              items.multilingualTextList[multilingual] &&
+              items.multilingualTextList[multilingual].length > 0
+            "
             :key="indexs"
             class="multilingual"
           >
@@ -1397,6 +1401,7 @@ export default {
     padding: 8px;
     margin-top: -130px;
     overflow: auto;
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 15%);
   }
 
   .multilingual {

+ 7 - 2
src/views/book/courseware/preview/components/dialogue_article/PhraseModelChs.vue

@@ -411,7 +411,12 @@
         </div>
         <template v-for="(items, indexs) in curQue.detail">
           <div
-            v-if="curQue.property.multilingual_position === 'all' && items.multilingualTextList[multilingual]"
+            v-if="
+              curQue.property.multilingual_position === 'all' &&
+              items.multilingualTextList &&
+              items.multilingualTextList[multilingual] &&
+              items.multilingualTextList[multilingual].length > 0
+            "
             :key="indexs"
             class="multilingual"
           >
@@ -1052,7 +1057,7 @@ export default {
       if (answer) {
         let writeModel = this.curQue.Bookanswer.writeModel;
         let hz = answer.hz;
-        if (writeModel.hasOwn(hz)) {
+        if (writeModel.hasOwnProperty(hz)) {
           writeModel[hz].push(answer);
         } else {
           writeModel[hz] = [answer];

+ 6 - 1
src/views/book/courseware/preview/components/dialogue_article/Practicechs.vue

@@ -648,7 +648,12 @@
     </template>
     <template v-for="(items, indexs) in curQue.detail">
       <div
-        v-if="curQue.property.multilingual_position === 'all' && items.multilingualTextList[multilingual]"
+        v-if="
+          curQue.property.multilingual_position === 'all' &&
+          items.multilingualTextList &&
+          items.multilingualTextList[multilingual] &&
+          items.multilingualTextList[multilingual].length > 0
+        "
         :key="indexs"
         class="multilingual"
       >

+ 7 - 2
src/views/book/courseware/preview/components/dialogue_article/WordModelChs.vue

@@ -389,7 +389,12 @@
         </div>
         <template v-for="(items, indexs) in curQue.detail">
           <div
-            v-if="curQue.property.multilingual_position === 'all' && items.multilingualTextList[multilingual]"
+            v-if="
+              curQue.property.multilingual_position === 'all' &&
+              items.multilingualTextList &&
+              items.multilingualTextList[multilingual] &&
+              items.multilingualTextList[multilingual].length > 0
+            "
             :key="indexs"
             class="multilingual"
           >
@@ -947,7 +952,7 @@ export default {
       if (answer) {
         let writeModel = this.curQue.Bookanswer.writeModel;
         let hz = answer.hz;
-        if (writeModel.hasOwn(hz)) {
+        if (writeModel.hasOwnProperty(hz)) {
           writeModel[hz].push(answer);
         } else {
           writeModel[hz] = [answer];

+ 28 - 16
src/views/book/courseware/preview/components/dialogue_article/index.vue

@@ -79,8 +79,8 @@
                     ? data.unified_attrib.topic_color
                     : '',
               }"
-              ><svg-icon icon-class="icon-article-ci" size="24" />本课生词</a
-            >
+              ><svg-icon icon-class="icon-article-ci" size="24"
+            /></a>
             <a
               v-if="data.property.is_enable_read === 'true'"
               @click="handleSwitchChange('showPhrases', 'showWord', 'showPractice')"
@@ -90,8 +90,8 @@
                     ? data.unified_attrib.topic_color
                     : '',
               }"
-              ><svg-icon icon-class="icon-article-practice" size="24" />语音练习</a
-            >
+              ><svg-icon icon-class="icon-article-practice" size="24"
+            /></a>
             <a
               v-if="data.property.is_enable_word === 'true'"
               @click="handleSwitchChange('showPhrases', 'showPractice', 'showWord')"
@@ -101,8 +101,8 @@
                     ? data.unified_attrib.topic_color
                     : '',
               }"
-              ><svg-icon icon-class="icon-article-phrase" size="24" />取词</a
-            >
+              ><svg-icon icon-class="icon-article-phrase" size="24"
+            /></a>
           </div>
         </div>
         <div ref="ArticleViewbody" class="ArticleView-body">
@@ -194,13 +194,19 @@
           />
         </div>
       </div>
-      <template v-if="data.new_word_list.new_word_list.length > 0">
+      <template
+        v-if="data.new_word_list && data.new_word_list.new_word_list && data.new_word_list.new_word_list.length > 0"
+      >
         <NewWordPreview :new-data="data.new_word_list" />
       </template>
-      <template v-if="data.other_word_list.new_word_list.length > 0">
+      <template
+        v-if="
+          data.other_word_list && data.other_word_list.new_word_list && data.other_word_list.new_word_list.length > 0
+        "
+      >
         <NewWordPreview :new-data="data.other_word_list" />
       </template>
-      <template v-if="data.notes_list.option.length > 0">
+      <template v-if="data.notes_list && data.notes_list.option && data.notes_list.option.length > 0">
         <NotesPreview :notes-data="data.notes_list" />
       </template>
     </div>
@@ -286,10 +292,10 @@ export default {
       handler(val) {
         if (val) {
           this.data.multilingual.forEach((item) => {
-            let trans_arr = item.translation.split('\n');
+            let trans_arr = item.translation ? item.translation.split('\n') : [];
             this.data.detail.forEach((items) => {
               let items_trans_arr = [];
-              if (!items.hasOwn('multilingualTextList')) {
+              if (items && !items.hasOwnProperty('multilingualTextList')) {
                 this.$set(items, 'multilingualTextList', {});
               }
               if (items.para) {
@@ -465,10 +471,10 @@ export default {
       }
       if (this.showLang) {
         this.data.multilingual.forEach((item) => {
-          let trans_arr = item.translation.split('\n');
+          let trans_arr = item.translation ? item.translation.split('\n') : [];
           this.data.detail.forEach((items) => {
             let items_trans_arr = [];
-            if (!items.hasOwn('multilingualTextList')) {
+            if (!items.hasOwnProperty('multilingualTextList')) {
               this.$set(items, 'multilingualTextList', {});
             }
             if (items.para) {
@@ -488,9 +494,15 @@ export default {
       this.NNPENewWordList = (
         this.data.new_word_list_other_component_input ? this.data.new_word_list_other_component_input : []
       )
-        .concat(this.data.new_word_list.new_word_list)
-        .concat(this.data.other_word_list.new_word_list);
-      this.NNPEAnnotationList = this.data.notes_list.option;
+        .concat(
+          this.data.new_word_list && this.data.new_word_list.new_word_list ? this.data.new_word_list.new_word_list : [],
+        )
+        .concat(
+          this.data.other_word_list && this.data.other_word_list.new_word_list
+            ? this.data.other_word_list.new_word_list
+            : [],
+        );
+      this.NNPEAnnotationList = this.data.notes_list && this.data.notes_list.option ? this.data.notes_list.option : [];
       let resArr = [];
       let sentArrTotal = [];
       let timeArr = [];

+ 7 - 0
src/views/book/courseware/preview/components/h5_games/H5GamesPreview.vue

@@ -37,6 +37,13 @@ export default {
       full_type: false,
     };
   },
+  watch: {
+    'data.file_list.length': {
+      handler() {
+        this.initData();
+      },
+    },
+  },
   created() {
     this.initData();
   },

+ 205 - 47
src/views/book/courseware/preview/components/new_word/NewWordPreview.vue

@@ -5,28 +5,58 @@
 
     <div v-if="data" class="main">
       <div class="NPC-zhedie" :style="{ width: width }">
-        <div
-          class="topTitle"
-          :style="{
+        <!-- :style="{
             backgroundColor:
               data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '',
-          }"
-        >
+          }" -->
+        <div v-if="data.title_con" class="topTitle">
           <div class="NPC-top-left">
             <span class="NPC-topTitle-text" v-html="data.title_con"></span>
             <span v-if="showLang" class="NPC-topTitle-text">
               {{ titleTrans[getLang()] }}
             </span>
           </div>
+        </div>
+        <div class="topTitle">
+          <div class="NPC-top-left">
+            <SvgIcon
+              icon-class="icon-full"
+              size="24"
+              :style="{
+                color:
+                  data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '#de4444',
+                cursor: 'pointer',
+              }"
+              @click="showCard = true"
+            />
+          </div>
 
           <div class="NPC-top-right">
-            <SvgIcon v-if="is_list" icon-class="icon-card" size="24" @click="is_list = false" />
+            <SvgIcon
+              v-if="is_list"
+              icon-class="icon-card"
+              size="24"
+              :style="{
+                color:
+                  data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '#de4444',
+              }"
+              @click="is_list = false"
+            />
             <!-- <img v-if="is_list" src="@/assets/newWord_list.png" alt="" @click="is_list = false" /> -->
             <!-- <img v-else src="@/assets/newWord_tile.png" alt="" @click="is_list = true" /> -->
-            <SvgIcon v-else icon-class="icon-park" size="24" @click="is_list = true" />
-            <span class="NPC-top-right-text" @click="handleChangeTab">{{ wordShow ? '收起' : '展开' }}</span>
+            <SvgIcon
+              v-else
+              icon-class="icon-park"
+              size="24"
+              :style="{
+                color:
+                  data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '#de4444',
+              }"
+              @click="is_list = true"
+            />
+            <!-- <span class="NPC-top-right-text" @click="handleChangeTab">{{ wordShow ? '收起' : '展开' }}</span>
             <img v-if="wordShow" src="@/assets/down.png" alt="" @click="handleChangeTab" />
-            <img v-else class="rotate" src="@/assets/down.png" alt="" @click="handleChangeTab" />
+            <img v-else class="rotate" src="@/assets/down.png" alt="" @click="handleChangeTab" /> -->
           </div>
         </div>
         <el-collapse-transition>
@@ -86,6 +116,7 @@
                       <SvgIcon
                         v-else
                         icon-class="play-btn"
+                        size="24"
                         :style="{
                           color:
                             data.unified_attrib && data.unified_attrib.topic_color
@@ -114,6 +145,7 @@
                         <SvgIcon
                           v-else
                           icon-class="play-btn"
+                          size="24"
                           :style="{
                             color:
                               data.unified_attrib && data.unified_attrib.topic_color
@@ -138,6 +170,10 @@
                               data.unified_attrib && data.unified_attrib.topic_color
                                 ? data.unified_attrib.topic_color
                                 : '',
+                            marginTop:
+                              data.unified_attrib && data.unified_attrib.font_size
+                                ? (((data.unified_attrib.font_size.replace('pt', '') * 1.5) / 3) * 4 - 16) / 2 + 'px'
+                                : '',
                           }"
                           >{{ index + 1 }}</b
                         >
@@ -385,6 +421,18 @@
           </template>
           <template v-else>
             <div v-if="data.new_word_list && data.new_word_list.length > 0" v-show="wordShow" class="NPC-word-tile">
+              <div v-if="data.audio_data.file_id" class="aduioLine-box" style="height: 0; margin: 0; overflow: hidden">
+                <AudioLine
+                  ref="audioLine"
+                  :audio-id="'newWordAudio' + indexStr"
+                  :mp3="data.audio_data.url"
+                  :get-cur-time="getCurTime"
+                  :ed="ed"
+                  type="audioLine"
+                  :attrib="data.unified_attrib"
+                  @handleListenRead="handleListenRead"
+                />
+              </div>
               <div v-for="(item, index) in data.new_word_list" :key="index" class="NPC-word-tile-item">
                 <div class="writeTop" :class="{ flipped: item.isFlipped }">
                   <div
@@ -474,8 +522,43 @@
                                 ? data.unified_attrib.topic_color
                                 : '',
                           }"
-                          :file-id="item.mp3_list"
+                          :file-id="item.mp3_list_url"
                         />
+                        <template v-else-if="item.bg || item.ed">
+                          <div
+                            class="audio-wrapper"
+                            :style="{
+                              background:
+                                data.unified_attrib && data.unified_attrib.topic_color
+                                  ? data.unified_attrib.topic_color
+                                  : '',
+                              display: 'flex',
+                              alignItems: 'center',
+                              justifyContent: 'center',
+                            }"
+                            @click="handleChangeTime(item.bg, item.ed)"
+                          >
+                            <SvgIcon
+                              v-if="curTime >= item.bg && curTime < item.ed && stopAudioS"
+                              icon-class="animated"
+                              size="14"
+                              :style="{
+                                color: '#fff',
+                              }"
+                            />
+                            <SvgIcon
+                              v-else
+                              icon-class="play-btn"
+                              size="14"
+                              :style="{
+                                color: '#fff',
+                                width: '14px',
+                                height: '14px',
+                              }"
+                            />
+                          </div>
+                        </template>
+
                         <p
                           v-if="item.pinyin && item.pinyin.split(' ').length === 1"
                           :style="{
@@ -483,10 +566,6 @@
                               data.unified_attrib && data.unified_attrib.topic_color
                                 ? data.unified_attrib.topic_color
                                 : '',
-                            fontSize:
-                              data.unified_attrib && data.unified_attrib.pinyin_size
-                                ? data.unified_attrib.pinyin_size
-                                : '',
                           }"
                           class="pinyin-box"
                         >
@@ -501,10 +580,6 @@
                                   data.unified_attrib && data.unified_attrib.topic_color
                                     ? data.unified_attrib.topic_color
                                     : '',
-                                fontSize:
-                                  data.unified_attrib && data.unified_attrib.pinyin_size
-                                    ? data.unified_attrib.pinyin_size
-                                    : '',
                               }"
                             >
                               {{ item.pinyin.split(' ')[indexh] ? item.pinyin.split(' ')[indexh] : '' }}
@@ -554,14 +629,15 @@
                         </div>
                       </div>
                     </div>
+                    <!-- width:
+                          !(item.collocation || item.liju_list) && item.new_word.length < 4
+                            ? item.hz_info.length * 86 + 'px'
+                            : '', -->
                     <div
                       v-if="item.collocation || item.liju_list || item.definition_list || item.cixing"
                       class="definition_list-box"
                       :style="{
-                        width:
-                          !(item.collocation || item.liju_list) && item.new_word.length < 4
-                            ? item.hz_info.length * 86 + 'px'
-                            : '',
+                        width: !(item.collocation || item.liju_list) && item.new_word.length < 4 ? '160px' : '',
                         margin:
                           !(item.collocation || item.liju_list) && item.new_word.length < 4 ? '16px auto 0 auto' : '',
                       }"
@@ -610,6 +686,28 @@
         <audio ref="newwordAudio"></audio>
       </div>
     </div>
+    <el-dialog title="" :visible.sync="showCard" width="100%" class="wordCard-dialog" top="0">
+      <i class="el-icon-arrow-left" :class="[showIndex === 0 ? 'disabled' : '']" @click="changeShowIndex('-')"></i>
+      <writeTableZoom
+        ref="writeTableZoom"
+        :edit-cardflag="false"
+        :data="data.new_word_list[showIndex]"
+        :page-number="showIndex"
+        :total-number="0"
+        :is-preview="true"
+        :filt-cardflag="false"
+        :attrib="data.unified_attrib"
+        :url="data.audio_data.url"
+      />
+      <p style="width: 100%; font-size: 20px; color: #fff; text-align: center">
+        {{ showIndex + 1 }} / {{ data.new_word_list.length }}
+      </p>
+      <i
+        class="el-icon-arrow-right"
+        :class="[showIndex === data.new_word_list.length - 1 ? 'disabled' : '']"
+        @click="changeShowIndex('+')"
+      ></i>
+    </el-dialog>
   </div>
 </template>
 
@@ -626,6 +724,7 @@ import Strockplay from './components/Strockplay.vue';
 import Vue from 'vue';
 import Plugin from 'v-fit-columns';
 Vue.use(Plugin);
+import writeTableZoom from './components/writeTableZoom.vue';
 
 export default {
   name: 'NewWordPreview',
@@ -636,6 +735,7 @@ export default {
     WordPhraseDetail,
     AudioPlay,
     Strockplay,
+    writeTableZoom,
   },
   mixins: [PreviewMixin],
   inject: ['bookInfo'],
@@ -666,6 +766,8 @@ export default {
       multilingualTextList: {}, // 多语言对应的切割后的翻译
       titleTrans: {},
       width: 0,
+      showCard: false, // 卡片放大
+      showIndex: 0, // 卡片放大索引
     };
   },
   watch: {
@@ -678,7 +780,8 @@ export default {
               oldVal &&
               oldVal.new_word_list[0].new_word &&
               val.new_word_list[0].new_word !== oldVal.new_word_list[0].new_word) ||
-            (val && val.new_word_list[0].new_word && !oldVal)
+            (val && val.new_word_list[0].new_word && !oldVal) ||
+            (val && val.hasOwnProperty('unified_attrib') && oldVal && !oldVal.hasOwnProperty('unified_attrib'))
           ) {
             // this.wordShow = isEnable(this.data.property.is_word_show);
             this.initData();
@@ -690,13 +793,8 @@ export default {
     },
   },
   mounted() {
-    this.width = `${
-      document.querySelector('.preview-main').offsetWidth -
-      200 -
-      20 -
-      (this.data.property.sn_display_mode === 'true' ? 15 : 0) -
-      (this.newData ? 16 : 0)
-    }px`;
+    let totalWidth = document.querySelector('.newWord-preview').offsetWidth;
+    this.width = `${totalWidth - (this.data.property.sn_display_mode === 'true' ? 15 : 0) - (this.newData ? 16 : 0)}px`;
   },
   methods: {
     palyAudio(url, sIndex) {
@@ -911,6 +1009,16 @@ export default {
       item.show_left = !item.show_left;
       item.isFlipped = !item.isFlipped;
     },
+    changeShowIndex(type) {
+      if (type === '+') {
+        if (this.showIndex !== this.data.new_word_list.length - 1) {
+          this.showIndex++;
+        }
+      } else if (this.showIndex !== 0) {
+        this.showIndex--;
+      }
+      this.$refs.writeTableZoom.changeRota();
+    },
   },
 };
 </script>
@@ -992,7 +1100,8 @@ export default {
 
           > span {
             font-size: 16px;
-            line-height: 150%;
+
+            // line-height: 150%;
             color: #000;
           }
         }
@@ -1010,8 +1119,7 @@ export default {
   .NPC-word-list {
     padding: 20px 24px;
     border: 1px solid rgba(0, 0, 0, 10%);
-    border-top: none;
-    border-radius: 0 0 8px 8px;
+    border-radius: 8px;
   }
 
   .detail-icon {
@@ -1224,7 +1332,6 @@ export default {
     display: flex;
     flex-flow: wrap;
     gap: 20px;
-    padding: 20px 0;
   }
 
   .writeTop {
@@ -1459,9 +1566,9 @@ export default {
           }
 
           p {
-            margin: 0 0 8px;
+            margin: 0;
             font-family: 'League';
-            font-size: 18px;
+            font-size: 14px;
             font-feature-settings: 'cv01' on;
             line-height: 120%;
             color: #de4444;
@@ -1471,17 +1578,16 @@ export default {
 
       :deep .audio-wrapper {
         box-sizing: border-box;
-        width: 50px;
-        height: 50px;
-        padding: 13px;
+        width: 24px;
+        height: 24px;
         margin: 0 auto 8px;
         cursor: pointer;
         background: #f3f3f3;
         border-radius: 40px;
 
         .voice-play {
-          width: 24px;
-          height: 24px;
+          width: 14px;
+          height: 14px;
         }
       }
 
@@ -1508,7 +1614,8 @@ export default {
 
           p {
             flex: 1;
-            line-height: 0;
+
+            // line-height: 0;
             word-break: break-word;
             white-space: pre-wrap;
           }
@@ -1517,7 +1624,13 @@ export default {
 
       :deep p {
         margin: 0;
-        line-height: 1.2;
+
+        span,
+        b,
+        p {
+          font-size: 14px !important;
+          text-align: left !important; // 有的富文本里设置了居中对齐
+        }
       }
     }
 
@@ -1636,6 +1749,53 @@ export default {
     display: none;
   }
 }
+
+.wordCard-dialog {
+  display: flex;
+  align-items: center;
+
+  :deep .el-dialog {
+    box-shadow: none;
+  }
+
+  .el-icon-arrow-left,
+  .el-icon-arrow-right {
+    position: fixed;
+    top: 50%;
+    left: 200px;
+    margin-top: -20px;
+    font-size: 50px;
+    font-weight: bold;
+    color: #f2f2f2;
+    cursor: pointer;
+
+    &.disabled {
+      color: darkgrey;
+      cursor: not-allowed;
+    }
+  }
+
+  .el-icon-arrow-right {
+    right: 200px;
+    left: auto;
+  }
+
+  :deep .el-dialog {
+    background: transparent;
+  }
+
+  :deep .el-dialog__headerbtn {
+    position: fixed;
+    top: 20px;
+    right: 20px;
+  }
+
+  :deep .el-dialog__headerbtn .el-dialog__close {
+    font-size: 34px;
+    font-weight: bold;
+    color: #f2f3f5;
+  }
+}
 </style>
 <style lang="scss">
 .NPC-zhedie {
@@ -1644,11 +1804,9 @@ export default {
     justify-content: space-between;
     width: 100%;
     height: 48px;
-    padding-right: 16px;
-    padding-left: 24px;
     overflow: hidden;
-    background: #e35454;
-    border: 1px solid rgba(0, 0, 0, 10%);
+
+    // background: #e35454;
     border-radius: 8px 8px 0 0;
 
     .NPC-top-left {

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

@@ -6,7 +6,7 @@
         class="voice-play"
       />
     </span>
-    <audio :id="fileId" :ref="fileId" :src="url" preload="metadata"></audio>
+    <audio :id="fileId" :ref="fileId" :src="fileId" preload="metadata"></audio>
   </div>
 </template>
 

+ 82 - 22
src/views/book/courseware/preview/components/new_word/components/WordPhraseDetail.vue

@@ -154,6 +154,61 @@
                 </div>
               </div> -->
             </div>
+            <div :class="['liju', lijuPatternIndex == 1 ? 'KWIC_liju' : '']">
+              <div v-if="lijuPatternIndex == 1" class="sort_dv">
+                <!-- <span :class="['sort', sortIndex == 0 ? 'sele' : '']" @click="sortEvent(0, 'sentence_list_sort_left')">
+                  <img src="../../../../assets/NPC/analys-right.png" alt="" />
+                </span>
+                <span :class="['sort', sortIndex == 1 ? 'sele' : '']" @click="sortEvent(1, 'sentence_list_sort_mid')">
+                  <img src="../../../../assets/NPC/analys-center.png" alt="" />
+                </span>
+                <span :class="['sort', sortIndex == 2 ? 'sele' : '']" @click="sortEvent(2, 'sentence_list_sort_right')">
+                  <img src="../../../../assets/NPC/analys-left.png" alt="" />
+                </span>
+                <span class="down" @click="downloadImage">
+                  <img src="../../../../assets/NPC/download.png" alt="" />
+                </span> -->
+              </div>
+              <div class="liju_main">
+                <div v-for="(item, i) in CurrentList" :key="i">
+                  <div class="number">{{ i + 1 }}.</div>
+                  <div>
+                    <template v-if="lijuPatternIndex == 1">
+                      <el-tooltip effect="dark" placement="bottom">
+                        <div slot="content">
+                          {{ item.source_courseware_name_path }}
+                        </div>
+                        <p class="p1">
+                          {{ item.show_source_courseware_name_path }}
+                        </p>
+                      </el-tooltip>
+                      <div class="p2">
+                        <div v-for="(txt, indexs) in item.resArr" :key="indexs">
+                          <span v-for="(txts, indexs) in txt" :key="indexs" v-html="txts"></span>
+                        </div>
+                      </div>
+                      <!-- <p class="p2" v-html="item.resArr"></p> -->
+                    </template>
+                    <template v-else>
+                      <p v-html="item.res"></p>
+                      <p class="p2">
+                        {{ item.source_courseware_name_path }}
+                      </p>
+                    </template>
+                  </div>
+                </div>
+                <p
+                  v-if="CurrentList.length === 0"
+                  :style="{
+                    textAlign: 'center',
+                    margin: '0',
+                    padding: ' 16px 0 0 24px',
+                  }"
+                >
+                  暂无例句
+                </p>
+              </div>
+            </div>
           </div>
         </div>
       </div>
@@ -165,7 +220,7 @@
 <script>
 import Strockplayredline from './Strockplayredline.vue';
 import Audio from '../../voice_matrix/components/AudioRed.vue';
-import { GetBookWebSIContent } from '@/api/app';
+import { getCoursewareWordExampleSentenceList } from '@/api/article';
 
 export default {
   // import引入的组件需要注入到对象中才能使用
@@ -173,6 +228,7 @@ export default {
     Strockplayredline,
     Audio,
   },
+  inject: ['getSelectId'],
   props: [
     'data',
     'changeDetailIndex',
@@ -316,20 +372,19 @@ export default {
       this.loading1 = true;
       this.loading2 = true;
       this.loading3 = true;
-      let Mname = 'book-courseware_manager-GetCoursewareWordExampleSentenceList_Sort';
+      let currentTreeID = this.$route.params.id || this.getSelectId();
       // 获取本课的 本教材的 本套的 的例句
-      GetBookWebSIContent(Mname, {
-        courseware_id: this.currentTreeID, // 课件id
+      getCoursewareWordExampleSentenceList({
+        courseware_id: currentTreeID, // 课件id
         word: this.data.new_word, // 生词
         search_scope: 0, // 检索范围0 本课件  1本教材 2本套
-        is_contain_word_variants: false,
+        is_contain_word_variants: 'false',
         is_filter_repetitive_sentence: 'true',
-        book_publish_status: 1,
+        book_publish_status: -1,
         sort_mode: 'ASCENT',
         compare_mode: 'SOURCE',
         book_id_list: [],
-        is_contain_word_variants: false,
-        is_contain_stat_data: false,
+        is_contain_stat_data: 'false',
         sentence_fc_length_min: -1,
         sentence_fc_length_max: -1,
       })
@@ -369,18 +424,17 @@ export default {
             //   this.list1.sentence_list_sort_right
             // );
           }
-          GetBookWebSIContent(Mname, {
-            courseware_id: this.currentTreeID, // 课件id
+          getCoursewareWordExampleSentenceList({
+            courseware_id: currentTreeID, // 课件id
             word: this.data.new_word, // 生词
             search_scope: 1, // 检索范围0 本课件  1本教材 2本套
-            is_contain_word_variants: false,
+            is_contain_word_variants: 'false',
             is_filter_repetitive_sentence: 'true',
-            book_publish_status: 1,
+            book_publish_status: -1,
             sort_mode: 'ASCENT',
             compare_mode: 'SOURCE',
             book_id_list: [],
-            is_contain_word_variants: false,
-            is_contain_stat_data: false,
+            is_contain_stat_data: 'false',
             sentence_fc_length_min: -1,
             sentence_fc_length_max: -1,
           })
@@ -412,19 +466,17 @@ export default {
                 //   this.list2.sentence_list_sort_right
                 // );
               }
-
-              GetBookWebSIContent(Mname, {
-                courseware_id: this.currentTreeID, // 课件id
+              getCoursewareWordExampleSentenceList({
+                courseware_id: currentTreeID, // 课件id
                 word: this.data.new_word, // 生词
                 search_scope: 2, // 检索范围0 本课件  1本教材 2本套
-                is_contain_word_variants: false,
+                is_contain_word_variants: 'false',
                 is_filter_repetitive_sentence: 'true',
-                book_publish_status: 1,
+                book_publish_status: -1,
                 sort_mode: 'ASCENT',
                 compare_mode: 'SOURCE',
                 book_id_list: [],
-                is_contain_word_variants: false,
-                is_contain_stat_data: false,
+                is_contain_stat_data: 'false',
                 sentence_fc_length_min: -1,
                 sentence_fc_length_max: -1,
               })
@@ -456,7 +508,15 @@ export default {
                     //   this.list3.sentence_list_sort_right
                     // );
                   }
-                  this.CurrentList = JSON.parse(JSON.stringify(this.allList.sentence_list));
+                  this.CurrentList = JSON.parse(
+                    JSON.stringify(
+                      this.list1.sentence_list.length !== 0
+                        ? this.list1.sentence_list
+                        : this.list2.sentence_list.length !== 0
+                          ? this.list2.sentence_list
+                          : this.list3.sentence_list,
+                    ),
+                  );
                   this.loading3 = false;
                 })
                 .catch((err) => {

+ 880 - 0
src/views/book/courseware/preview/components/new_word/components/writeTableZoom.vue

@@ -0,0 +1,880 @@
+<template>
+  <div v-if="data" :class="['writeTable', editCardflag ? '' : 'writeTable-preview writeTable-preview-' + totalNumber]">
+    <div class="writeTop" :class="{ flipped: isFlipped }">
+      <div
+        v-if="(isPreview && showLeft) || !isPreview"
+        class="left left-preview"
+        :class="[data.file_list[0] ? '' : 'left-big']"
+        :style="{
+          borderColor: attrib && attrib.topic_color ? attrib.topic_color : '',
+          padding:
+            data.new_word && (data.header_con || data.label)
+              ? ''
+              : !data.new_word && (data.header_con || data.label)
+                ? '40px 12px 0 12px'
+                : '12px',
+        }"
+      >
+        <div class="header-info-preview">
+          <h5 :style="{ textAlign: 'left' }">{{ data.header_con }}</h5>
+          <label :style="{ background: attrib && attrib.topic_color ? attrib.topic_color : '' }">{{
+            data.label
+          }}</label>
+        </div>
+        <div v-if="data.file_list[0]" class="item-image">
+          <el-image
+            :style="{
+              width: '568px',
+              height:
+                data.new_word && (data.header_con || data.label)
+                  ? '340px'
+                  : !data.new_word || data.header_con || data.label
+                    ? '382px'
+                    : '410px',
+            }"
+            :src="data.pic_url"
+            :preview-src-list="[data.pic_url]"
+            fit="contain"
+          />
+        </div>
+        <h2 v-if="data.new_word" :class="['con-preview', data.file_list[0] ? '' : 'con-preview-big']">
+          {{ data.new_word }}
+        </h2>
+
+        <a v-if="isPreview" class="overturn-btn" @click="changeShowLeft"><i class="el-icon-refresh"></i></a>
+      </div>
+      <div
+        v-if="(isPreview && !showLeft) || !isPreview"
+        class="right right-preview left-preview"
+        :class="[isPreview ? 'right-preview-rota' : '']"
+        :style="{
+          borderColor: attrib && attrib.topic_color ? attrib.topic_color : '',
+          paddingTop: data.collocation || data.liju_list || data.definition_list ? '' : '80px',
+        }"
+      >
+        <div class="header-info-preview">
+          <h5 :style="{ textAlign: 'left' }">{{ data.header_con }}</h5>
+          <label :style="{ background: attrib && attrib.topic_color ? attrib.topic_color : '' }">{{
+            data.label
+          }}</label>
+        </div>
+        <div
+          :style="{
+            display: 'flex',
+            justifyContent: !(data.collocation && data.liju_list) && data.new_word.length < 4 ? 'center' : 'auto',
+            columnGap: '16px',
+          }"
+        >
+          <div v-if="data.hz_info.length > 0" style="width: max-content">
+            <AudioPlay
+              v-if="data.mp3_list"
+              :style="{
+                background: attrib && attrib.topic_color ? attrib.topic_color : '',
+              }"
+              :file-id="data.mp3_list_url"
+            />
+            <template v-else-if="data.bg || data.ed">
+              <div
+                class="audio-wrapper"
+                :style="{
+                  background: attrib && attrib.topic_color ? attrib.topic_color : '',
+                  display: 'flex',
+                  alignItems: 'center',
+                  justifyContent: 'center',
+                }"
+                @click="handleChangeTime(data.bg, data.ed)"
+              >
+                <SvgIcon
+                  v-if="curTime >= data.bg && curTime < data.ed && stopAudioS"
+                  icon-class="animated"
+                  size="30"
+                  :style="{
+                    color: '#fff',
+                  }"
+                />
+                <SvgIcon
+                  v-else
+                  icon-class="play-btn"
+                  size="30"
+                  :style="{
+                    color: '#fff',
+                    width: '30px',
+                    height: '30px',
+                  }"
+                />
+              </div>
+            </template>
+            <p
+              v-if="data.pinyin && data.pinyin.split(' ').length === 1"
+              :style="{ color: attrib && attrib.topic_color ? attrib.topic_color : '' }"
+              class="pinyin-box"
+            >
+              {{ data.pinyin }}
+            </p>
+            <div class="hz-box">
+              <div v-for="(itemh, indexh) in data.hz_info" :key="indexh" class="hz-item">
+                <p
+                  v-if="data.pinyin && data.pinyin.split(' ').length > 1"
+                  :style="{ color: attrib && attrib.topic_color ? attrib.topic_color : '' }"
+                >
+                  {{ data.pinyin.split(' ')[indexh] ? data.pinyin.split(' ')[indexh] : '' }}
+                </p>
+                <Strockplay
+                  class-name="adult-strockplay"
+                  :Book_text="itemh.con"
+                  :play-storkes="true"
+                  :stroke-play-color="attrib && attrib.topic_color ? attrib.topic_color : '#f44444'"
+                  :stroke-color="'#000000'"
+                  :paly-width="'24px'"
+                  :BoxbgType="'0'"
+                  :cur-item="itemh.hzDetail.hz_json"
+                  :target-div="'writeTops-item-zoom-' + pageNumber + '-' + indexh + '-' + itemh.con + '-' + totalNumber"
+                  :class="[indexh !== 0 ? 'writeTop-item-noLeft' : '']"
+                  class="writeTop-item"
+                  :style="{ borderColor: attrib && attrib.topic_color ? attrib.topic_color : '#f44444' }"
+                />
+              </div>
+            </div>
+          </div>
+
+          <div
+            v-if="(data.collocation || data.liju_list) && data.new_word.length < 4"
+            class="definition-box"
+            :style="{
+              flex: '1',
+              marginTop: data.mp3_list || data.bg || data.ed ? '104px' : '36px',
+            }"
+          >
+            <div v-if="data.cixing">
+              <label class="card-label">词性:</label>
+              <p v-html="data.cixing"></p>
+            </div>
+            <div v-if="data.definition_list">
+              <label class="card-label">释义:</label>
+              <p v-html="data.definition_list"></p>
+            </div>
+          </div>
+        </div>
+        <div
+          v-if="data.collocation || data.liju_list || data.definition_list || data.cixing"
+          class="definition-box"
+          :style="{
+            width:
+              !(data.collocation || data.liju_list) && data.new_word.length < 4
+                ? data.new_word.length > 2
+                  ? data.hz_info.length * 150 + 'px'
+                  : '290px'
+                : '',
+            margin: !(data.collocation || data.liju_list) && data.new_word.length < 4 ? '16px auto 0 auto' : '',
+          }"
+        >
+          <template v-if="!(data.collocation || data.liju_list) || data.new_word.length >= 4">
+            <div v-if="data.cixing">
+              <label class="card-label">词性:</label>
+              <p v-html="data.cixing"></p>
+            </div>
+            <div v-if="data.definition_list">
+              <label class="card-label">释义:</label>
+              <p v-html="data.definition_list"></p>
+            </div>
+          </template>
+          <div v-if="data.collocation">
+            <label class="card-label">搭配:</label>
+            <p v-html="data.collocation"></p>
+          </div>
+          <div v-if="data.liju_list">
+            <label class="card-label">例句:</label>
+            <p v-html="data.liju_list"></p>
+          </div>
+        </div>
+        <a v-if="isPreview" class="overturn-btn" @click="changeShowLeft"><i class="el-icon-refresh"></i></a>
+      </div>
+    </div>
+    <div v-if="url" class="aduioLine-box" style="height: 0; margin: 0; overflow: hidden">
+      <AudioLine
+        ref="audioLine"
+        :audio-id="'newWordAudioZoom'"
+        :mp3="url"
+        :get-cur-time="getCurTime"
+        :ed="ed"
+        type="audioLine"
+        :attrib="attrib"
+        @handleListenRead="handleListenRead"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其它文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+import AudioPlay from './AudioPlay.vue';
+import Strockplay from './Strockplay.vue';
+import AudioLine from '../../voice_matrix/components/AudioLine.vue';
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    Strockplay,
+    AudioPlay,
+    AudioLine,
+  },
+  props: [
+    'isPreview',
+    'data',
+    'pageNumber',
+    'totalNumber',
+    'editCardflag',
+    'is_preview',
+    'filtCardflag',
+    'attrib',
+    'url',
+  ],
+  data() {
+    // 这里存放数据
+    return {
+      loading: false,
+      isFlipped: false,
+      showLeft: true,
+      curTime: 0,
+      stopAudioS: false,
+    };
+  },
+  // 计算属性 类似于data概念
+  computed: {},
+  // 监控data中数据变化
+  watch: {
+    editCardflag: {
+      handler(val, oldVal) {
+        if (val != oldVal) {
+          this.showLeft = true;
+          this.isFlipped = false;
+        }
+      },
+      deep: true,
+    },
+    filtCardflag: {
+      handler(val, oldVal) {
+        this.showLeft = true;
+        this.isFlipped = false;
+      },
+      deep: true,
+    },
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created() {},
+  // 生命周期 - 挂载完成(可以访问DOM元素)
+  mounted() {},
+  // 生命周期-挂载之前
+  beforeMount() {},
+  // 生命周期-更新之后
+  updated() {},
+  // 如果页面有keep-alive缓存功能,这个函数会触发
+  activated() {},
+  // 方法集合
+  methods: {
+    // 翻面
+    changeShowLeft() {
+      this.showLeft = !this.showLeft;
+      this.isFlipped = !this.isFlipped;
+    },
+    // 点击播放某个句子
+    handleChangeTime(time, edTime) {
+      this.curTime = time;
+      this.stopAudioS = true;
+      this.$refs.audioLine.onTimeupdateTime(time / 1000, true);
+      this.ed = edTime;
+    },
+    handleListenRead(playFlag) {
+      this.stopAudioS = playFlag;
+    },
+    getCurTime(curTime) {
+      this.curTime = curTime * 1000;
+    },
+  },
+  // 生命周期-创建之前
+  beforeCreated() {},
+  // 生命周期-更新之前
+  beforUpdate() {},
+  // 生命周期-销毁之前
+  beforeDestory() {},
+  // 生命周期-销毁完成
+  destoryed() {},
+};
+</script>
+<style lang="scss" scoped>
+.writeTable {
+  // height: 842px;
+  box-sizing: border-box;
+  width: 1208px;
+  margin: 40px auto 19px;
+  perspective: 1000px;
+
+  &-preview {
+    width: 600px;
+  }
+
+  .writeTop {
+    position: relative;
+    display: flex;
+    column-gap: 8px;
+    min-height: 450px;
+    transition: 0.6s;
+    perspective: 1000px;
+    transform-style: preserve-3d;
+
+    .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;
+          }
+        }
+      }
+    }
+
+    .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: 24px;
+        font-weight: 400;
+        line-height: 32px;
+        color: #000;
+      }
+
+      label {
+        position: absolute;
+        top: -4px;
+        right: -4px;
+        padding: 0 16px 0 8px;
+        font-size: 24px;
+        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-size: 22px;
+        font-feature-settings: 'cv01' on;
+        line-height: 120%;
+        color: #de4444;
+        text-align: center;
+      }
+
+      .hz-box {
+        width: 100%;
+
+        .hz-item {
+          text-align: center;
+
+          :deep .strockplayInner {
+            width: 98px;
+            height: 98px;
+          }
+
+          p {
+            margin-bottom: 12px;
+            font-family: 'League';
+            font-size: 22px;
+            font-feature-settings: 'cv01' on;
+            line-height: 120%;
+            color: #de4444;
+          }
+        }
+      }
+
+      :deep .audio-wrapper {
+        box-sizing: border-box;
+        width: 60px;
+        height: 60px;
+        padding: 18px;
+        margin: 0 auto 8px;
+        cursor: pointer;
+        background: #f3f3f3;
+        border-radius: 40px;
+
+        .voice-play {
+          width: 30px;
+          height: 30px;
+        }
+      }
+
+      .definition-box {
+        margin-top: 16px;
+        white-space: pre;
+
+        > div {
+          display: flex;
+          margin-bottom: 8px;
+
+          label,
+          p {
+            width: 40px;
+            font-size: 20px;
+            font-weight: 400;
+            line-height: 150%;
+            color: #000;
+          }
+
+          label {
+            width: 54px;
+          }
+
+          p {
+            flex: 1;
+            word-break: break-word;
+            white-space: pre-wrap;
+          }
+        }
+      }
+
+      :deep p {
+        margin: 0;
+
+        span,
+        b,
+        p {
+          text-align: left !important; // 有的富文本里设置了居中对齐
+        }
+      }
+    }
+
+    .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: 46px;
+      font-weight: 400;
+      line-height: 100%;
+      color: #000;
+      text-align: center;
+
+      &-big {
+        margin: 0;
+        font-size: 86px;
+      }
+    }
+
+    .writeTop-row {
+      display: flex;
+      justify-content: center;
+    }
+  }
+
+  .writeTop-nopadding {
+    height: 842px;
+    padding-top: 0;
+  }
+
+  .item-info {
+    box-sizing: border-box;
+    display: flex;
+    column-gap: 16px;
+    width: 100%;
+    padding: 0 46px 8px;
+
+    &-left {
+      .writeTop-item {
+        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 {
+      display: flex;
+      column-gap: 4px;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      height: 32px;
+
+      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 {
+        height: 32px;
+        background-color: #f3f3f3;
+        border: none;
+        outline: none;
+      }
+    }
+
+    .item-info-half,
+    .item-info-all {
+      display: flex;
+      width: 50%;
+      height: 22px;
+      font-size: 14px;
+      line-height: 22px;
+    }
+
+    .item-info-all {
+      width: 100%;
+    }
+  }
+
+  .hz-box {
+    display: flex;
+    width: max-content;
+  }
+
+  .writeTop-item {
+    border: 1px solid #de4444;
+  }
+
+  .writeTop-item-noLeft {
+    border-left: none;
+  }
+
+  .tian-div {
+    position: relative;
+    width: 100%;
+    height: 100%;
+
+    .tian {
+      width: 100%;
+      height: 100%;
+    }
+
+    img {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  .flipped {
+    transform: rotateY(180deg);
+  }
+
+  .flipped-back {
+    transform: rotateY(180deg);
+  }
+}
+</style>
+<style lang="scss">
+.writeTable {
+  input::placeholder {
+    font-family: initial;
+  }
+
+  input::input-placeholder {
+    font-family: initial;
+  }
+
+  input::placeholder {
+    font-family: initial;
+  }
+
+  input:placeholder {
+    font-family: initial;
+  }
+
+  input:input-placeholder {
+    font-family: initial;
+  }
+
+  .header-info {
+    input::placeholder {
+      font-size: 16px;
+    }
+
+    input::input-placeholder {
+      font-size: 16px;
+    }
+
+    input::placeholder {
+      font-size: 16px;
+    }
+
+    input:placeholder {
+      font-size: 16px;
+    }
+
+    input:input-placeholder {
+      font-size: 16px;
+    }
+  }
+}
+</style>

+ 11 - 13
src/views/book/courseware/preview/components/notes/NotesPreview.vue

@@ -5,24 +5,22 @@
 
     <div class="main">
       <div class="NPC-zhedie">
-        <div
-          class="topTitle"
-          :style="{
+        <!-- :style="{
             backgroundColor:
               data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '',
-          }"
-        >
+          }" -->
+        <div class="topTitle-note" v-if="data.title_con">
           <div class="NPC-top-left">
             <span class="NPC-topTitle-text" v-html="data.title_con"></span>
             <span v-if="showLang" class="NPC-topTitle-text">
               {{ titleTrans[getLang()] }}
             </span>
           </div>
-          <div class="NPC-top-right" @click="handleChangeTab">
+          <!-- <div class="NPC-top-right" @click="handleChangeTab">
             <span class="NPC-top-right-text">{{ wordShow ? '收起' : '展开' }}</span>
             <img v-if="wordShow" src="@/assets/down.png" alt="" />
             <img v-else class="rotate" src="@/assets/down.png" alt="" />
-          </div>
+          </div> -->
         </div>
         <el-collapse-transition>
           <div v-show="wordShow" class="NPC-notes-list">
@@ -222,7 +220,7 @@ export default {
   .NPC-zhedie {
     // margin-bottom: 24px;
 
-    .topTitle {
+    .topTitle-note {
       display: flex;
       justify-content: space-between;
       width: 100%;
@@ -230,9 +228,10 @@ export default {
       padding-right: 16px;
       padding-left: 24px;
       overflow: hidden;
-      background: #e35454;
-      border: 1px solid rgba(0, 0, 0, 10%);
-      border-radius: 8px 8px 0 0;
+
+      // background: #e35454;
+      // border: 1px solid rgba(0, 0, 0, 10%);
+      // border-radius: 8px 8px 0 0;
 
       :deep p {
         margin: 0;
@@ -283,8 +282,7 @@ export default {
     .NPC-notes-list {
       padding: 24px 24px 5px;
       border: 1px solid rgba(0, 0, 0, 10%);
-      border-top: none;
-      border-radius: 0 0 8px 8px;
+      border-radius: 8px;
 
       .NPC-notes {
         width: 100%;

+ 567 - 27
src/web_preview/index.vue

@@ -26,23 +26,18 @@
       <aside v-if="navigationShow" class="left-menu">
         <div class="courseware-info">
           <div class="cover-image">
-            <img v-if="project.cover_image_file_url.length > 0" :src="project.cover_image_file_url" alt="" />
+            <img v-if="project.cover_image_file_url.length > 0" :src="project.cover_image_file_url" alt="cover-image" />
           </div>
           <div class="info-content">
-            <div class="catalogue-icon">
+            <div class="catalogue-icon" @click="toggleNavigationShow">
               <SvgIcon icon-class="catalogue" size="54" />
             </div>
             <div class="courseware">
               <div class="name nowrap-ellipsis" :title="courseware_info.book_name">
                 {{ courseware_info.book_name }}
               </div>
-              <div>
-                <span>主编 </span>
-                <span>{{ project.editor }}</span>
-              </div>
-              <div>
-                <span>副主编 </span>
-                <span>{{ project.associate_editor }}</span>
+              <div class="editor" :title="project.editor">
+                {{ project.editor }}
               </div>
             </div>
           </div>
@@ -63,8 +58,13 @@
         </div>
       </aside>
 
-      <div ref="previewMain" class="main-container">
-        <div v-if="!navigationShow" class="catalogue-bar">
+      <div
+        ref="previewMain"
+        class="main-container"
+        :style="{ paddingLeft: navigationShow ? '15px' : '315px', paddingRight: sidebarShow ? '15px' : '315px' }"
+      >
+        <!-- 左侧菜单栏 - 收缩 -->
+        <div v-if="!navigationShow" class="catalogue-bar" @click="toggleNavigationShow">
           <SvgIcon icon-class="catalogue" size="54" />
         </div>
 
@@ -81,23 +81,130 @@
             :background="background"
             :can-remark="isTrue(courseware_info.is_my_audit_task) && isTrue(courseware_info.is_can_add_audit_remark)"
             :show-remark="false"
+            :project="project"
             :component-remark-obj="remark_list_obj"
             @computeScroll="computeScroll"
           />
           <div class="preview-right"></div>
         </main>
+
+        <!-- 右侧菜单栏 - 收缩 -->
+        <aside v-if="!sidebarShow" class="sidebar-bar">
+          <aside class="toolbar">
+            <div class="toolbar-special">
+              <!-- <img :src="require('@/assets/icon/sidebar-fullscreen.png')" alt="全屏" />
+              <img :src="require('@/assets/icon/sidebar-toolkit.png')" alt="工具箱" /> -->
+              <img :src="require(`@/assets/icon/arrow-down.png`)" alt="伸缩" @click="toggleSidebarShow" />
+            </div>
+          </aside>
+        </aside>
       </div>
 
-      <div class="back-top" @click="backTop">
+      <div v-if="!sidebarShow" class="back-top" @click="backTop">
         <img :src="require(`@/assets/icon/back-top.png`)" alt="返回顶部" />
       </div>
+
+      <!-- 右侧工具栏 -->
+      <aside v-if="sidebarShow" ref="sidebarMenu" class="sidebar">
+        <aside class="toolbar">
+          <div class="toolbar-special">
+            <!-- <img :src="require('@/assets/icon/sidebar-fullscreen.png')" alt="全屏" />
+            <img :src="require('@/assets/icon/sidebar-toolkit.png')" alt="工具箱" /> -->
+          </div>
+          <div v-if="sidebarShow" class="toolbar-list">
+            <div
+              v-for="{ icon, title, handle, param } in sidebarIconList"
+              :key="icon"
+              :class="['sidebar-item', { active: curToolbarIcon === icon }]"
+              :title="title"
+              @click="handleSidebarClick(handle, param, icon)"
+            >
+              <div
+                class="sidebar-icon icon-mask"
+                :style="{
+                  backgroundColor: curToolbarIcon === icon ? '#fff' : '#1E2129',
+                  maskImage: `url(${require(`@/assets/icon/sidebar-${icon}.png`)})`,
+                }"
+              ></div>
+            </div>
+          </div>
+          <div class="adjustable" @click="toggleSidebarShow">
+            <img :src="require(`@/assets/icon/arrow-up.png`)" alt="伸缩" />
+          </div>
+        </aside>
+        <div class="content">
+          <el-drawer
+            custom-class="custom-drawer"
+            :visible="drawerType.length > 0"
+            :with-header="false"
+            :modal="false"
+            size="240"
+            direction="ltr"
+            :style="drawerStyle"
+          >
+            <div class="infinite-list-wrapper">
+              <h5>{{ drawerTitle }}</h5>
+              <ul
+                v-infinite-scroll="loadMore"
+                class="scroll-container"
+                infinite-scroll-disabled="disabled"
+                :infinite-scroll-immediate="false"
+              >
+                <li
+                  v-for="(item, index) in file_list"
+                  :key="index"
+                  class="list-item"
+                  @click="handleFileClick(item?.courseware_id, item?.component_id)"
+                >
+                  <template v-if="parseInt(drawerType) === 0">
+                    <el-image :src="item.file_url" fit="contain" />
+                    <!-- <span class="text-box">{{ item.file_name.slice(0, item.file_name.lastIndexOf('.')) }}</span> -->
+                  </template>
+                  <template v-else-if="parseInt(drawerType) === 1">
+                    <AudioPlay
+                      view-size="middle"
+                      :file-id="item.file_id"
+                      :file-name="item.file_name.slice(0, item.file_name.lastIndexOf('.'))"
+                      :show-slider="true"
+                      :audio-index="index"
+                    />
+                  </template>
+                  <template v-else-if="parseInt(drawerType) === 2">
+                    <VideoPlay view-size="small" :file-id="item.file_id" :video-index="index" />
+                    <!-- <span class="text-box">{{ item.file_name.slice(0, item.file_name.lastIndexOf('.')) }}</span> -->
+                  </template>
+                </li>
+              </ul>
+              <p v-if="loading">加载中...</p>
+              <p v-if="noMore">没有更多了</p>
+            </div>
+          </el-drawer>
+        </div>
+
+        <div class="back-top" @click="backTop">
+          <img :src="require(`@/assets/icon/back-top.png`)" alt="返回顶部" />
+        </div>
+      </aside>
     </div>
+
+    <el-dialog title="" :visible="visibleMindMap" width="1100px" class="audit-dialog" @close="dialogClose('MindMap')">
+      <MindMap
+        v-if="isChildDataLoad"
+        ref="mindMapRef"
+        :project-id="projectId"
+        :mind-map-json-data="mindMapJsonData"
+        @child-click="handleNodeClick"
+      />
+    </el-dialog>
   </div>
 </template>
 
 <script>
 import CoursewarePreview from '@/views/book/courseware/preview/CoursewarePreview.vue';
 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';
 import * as OpenCC from 'opencc-js';
 
 import { GetBookCoursewareInfo, GetCoursewareAuditRemarkList, GetProjectInfo } from '@/api/project';
@@ -105,6 +212,8 @@ import {
   ContentGetCoursewareContent_View,
   ChapterGetBookChapterStructExpandList,
   GetBookBaseInfo,
+  MangerGetBookMindMap,
+  PageQueryBookResourceList,
   GetLanguageTypeList,
   GetBookUnifiedAttrib,
 } from '@/api/book';
@@ -113,6 +222,9 @@ export default {
   name: 'CommonPreview',
   components: {
     CoursewarePreview,
+    MindMap,
+    VideoPlay,
+    AudioPlay,
   },
   provide() {
     return {
@@ -120,12 +232,28 @@ export default {
       getChinese: () => this.chinese,
       getLangList: () => this.langList,
       convertText: this.convertText,
+      getProjectId: () => this.projectId,
+      getSelectId: () => this.select_node,
     };
   },
   data() {
+    const sidebarIconList = [
+      // { icon: 'search', title: '搜索', handle: '', param: {} },
+      { icon: 'mindmap', title: '思维导图', handle: 'openMindMap', param: {} },
+      // { icon: 'knowledge', title: '知识图谱', handle: '', param: {} },
+      // { icon: 'totalResources', title: '总资源', handle: '', param: {} },
+      // { icon: 'collect', title: '收藏', handle: '', param: {} },
+      { icon: 'audio', title: '音频', handle: 'openDrawer', param: { type: '1' } },
+      { icon: 'image', title: '图片', handle: 'openDrawer', param: { type: '0' } },
+      { icon: 'video', title: '视频', handle: 'openDrawer', param: { type: '2' } },
+      // { icon: 'note', title: '笔记', handle: '', param: {} },
+      // { icon: 'translate', title: '翻译', handle: '', param: {} },
+      // { icon: 'setting', title: '设置', handle: '', param: {} },
+    ];
+
     return {
-      book_id: this.$route.query.book_id || '',
-      select_node: '',
+      projectId: this.$route.query.book_id || '',
+      select_node: this.id,
       courseware_info: {
         book_name: '',
         is_can_start_edit: 'false',
@@ -156,6 +284,29 @@ export default {
       remark_content: '',
       submit_loading: false,
       isTrue,
+      menuPosition: {
+        x: -1,
+        y: -1,
+        componentId: 'WHOLE',
+      },
+      curToolbarIcon: this.isShowAudit ? 'audit' : '',
+      sidebarIconList,
+      visibleMindMap: false,
+      isChildDataLoad: false,
+      mindMapJsonData: {}, // 思维导图json数据
+      drawerType: '', // 抽屉类型
+      drawerStyle: {
+        top: '0',
+        height: '0',
+        right: '0',
+      },
+      page_capacity: 10,
+      cur_page: 1,
+      file_list: [],
+      total_count: 0,
+      loading: true,
+      lastLoadTime: 0,
+      minLoadInterval: 3 * 1000,
       isShowGroup: false,
       groupShowAll: true,
       opencc: OpenCC.Converter({ from: 'cn', to: 'tw' }),
@@ -167,14 +318,32 @@ export default {
       unified_attrib: {},
       curSelectId: this.id,
       navigationShow: true,
+      sidebarShow: true,
       project: {
-        editor: '', // 主编
-        associate_editor: '', // 副主编
+        editor: '', // 作者
         cover_image_file_id: null, // 封面图片ID
         cover_image_file_url: '', // 封面图片URL
       },
     };
   },
+  computed: {
+    disabled() {
+      const result = this.loading || this.noMore;
+      return result;
+    },
+    noMore() {
+      const result = this.file_list.length >= this.total_count;
+      return result;
+    },
+    drawerTitle() {
+      const titleMap = {
+        0: '图片资源',
+        1: '音频资源',
+        2: '视频资源',
+      };
+      return titleMap[this.drawerType] || '资源列表';
+    },
+  },
   watch: {
     isJudgeCorrect(newVal) {
       if (!newVal) {
@@ -186,6 +355,9 @@ export default {
       this.simulateAnswer();
     },
   },
+  mounted() {
+    this.calcDrawerPosition();
+  },
   created() {
     this.getBookBaseInfo();
     this.getBookChapterStructExpandList();
@@ -194,14 +366,25 @@ export default {
   },
   methods: {
     getBookBaseInfo() {
-      GetBookBaseInfo({ id: this.book_id }).then(({ book_info }) => {
+      GetBookBaseInfo({ id: this.projectId }).then(({ book_info }) => {
         this.courseware_info = { ...this.courseware_info, ...book_info, book_name: book_info.name };
+        if (book_info.cover_image_file_id) {
+          this.project.cover_image_file_url = book_info.cover_image_file_url;
+        }
+        if (book_info.editor) {
+          this.project.editor = book_info.editor;
+        }
+        if (book_info.cover_image_file_url) {
+          this.project.cover_image_file_url = book_info.cover_image_file_url;
+        }
       });
     },
 
     getProjectInfo() {
       GetProjectInfo({ id: this.projectId }).then(({ project_info }) => {
-        this.project = project_info;
+        if (project_info.cover_image_file_url) {
+          this.project = project_info;
+        }
       });
     },
 
@@ -252,7 +435,7 @@ export default {
      */
     getBookChapterStructExpandList() {
       ChapterGetBookChapterStructExpandList({
-        book_id: this.book_id,
+        book_id: this.projectId,
         node_deep_mode: 0,
         is_contain_producer: 'true',
         is_contain_auditor: 'true',
@@ -262,7 +445,7 @@ export default {
     },
 
     getBookUnifiedAttr() {
-      GetBookUnifiedAttrib({ book_id: this.book_id }).then(({ content }) => {
+      GetBookUnifiedAttrib({ book_id: this.projectId }).then(({ content }) => {
         if (content) {
           this.unified_attrib = JSON.parse(content);
         }
@@ -312,6 +495,150 @@ export default {
     },
 
     /**
+     * 处理侧边栏图标点击事件
+     * @param {string} handle - 处理函数名
+     * @param {any} param - 处理函数参数
+     * @param {string} icon - 图标名称
+     */
+    handleSidebarClick(handle, param, icon) {
+      if (typeof handle === 'string' && handle && typeof this[handle] === 'function') {
+        this[handle](param);
+      }
+      this.curToolbarIcon = icon;
+    },
+
+    openMindMap() {
+      MangerGetBookMindMap({ book_id: this.projectId }).then(({ content }) => {
+        if (content) {
+          this.mindMapJsonData = JSON.parse(content);
+          this.isChildDataLoad = true;
+        }
+      });
+      this.visibleMindMap = true;
+    },
+
+    async handleNodeClick(data) {
+      let [nodeId, componentId] = data.split('#');
+      if (nodeId) this.selectNode(nodeId);
+      if (componentId) {
+        let node = await this.$refs.courserware.findChildComponentByKey(componentId);
+        if (node) {
+          await this.$nextTick();
+          this.$refs.previewMain.scrollTo({
+            top: node.$el.offsetTop - 50,
+            left: node.$el.offsetLeft - 50,
+            behavior: 'smooth',
+          });
+        }
+      }
+      this.visibleMindMap = false;
+    },
+
+    // 计算抽屉滑出位置
+    calcDrawerPosition() {
+      const menu = this.$refs.sidebarMenu;
+      if (menu) {
+        const rect = menu.getBoundingClientRect();
+        this.drawerStyle = {
+          top: `${rect.top}px`,
+          height: `${rect.height}px`,
+          left: `${rect.right - 240}px`,
+        };
+      }
+    },
+    /**
+     * 打开抽屉并初始化加载
+     * @param {Object} param - 抽屉参数
+     * @param {string} param.type - 抽屉类型(0: 图片, 1: 音频, 2: 视频)
+     */
+    openDrawer({ type }) {
+      if (this.drawerType === type) {
+        this.drawerType = '';
+        return;
+      }
+      // 重置所有加载状态
+      this.resetLoadState();
+      this.drawerType = type; // 假设这是你的类型变量
+      this.$nextTick(() => {
+        // 确保DOM更新后触发加载
+        this.loadMore();
+      });
+    },
+    openAudit() {},
+    resetLoadState() {
+      this.cur_page = 1;
+      this.file_list = [];
+      this.total_count = 0;
+      this.loading = false;
+      this.lastLoadTime = 0; // 重置时间戳,允许立即加载
+      this.loadCount = 0;
+    },
+    // 加载更多数据
+    async loadMore() {
+      const now = Date.now();
+      // 只有当lastLoadTime不为0(不是第一次)且时间间隔太短时才return
+      if (this.lastLoadTime > 0 && now - this.lastLoadTime < this.minLoadInterval) {
+        return;
+      }
+
+      if (this.disabled || this.loading) {
+        if (this.lastLoadTime > 0) {
+          return;
+        }
+      }
+      this.loading = true;
+      const params = {
+        page_capacity: this.page_capacity,
+        cur_page: this.cur_page,
+        book_id: this.projectId,
+        type: parseInt(this.drawerType),
+      };
+      await PageQueryBookResourceList(params)
+        .then(({ total_count, resource_list }) => {
+          this.total_count = total_count;
+          // 记录加载前的滚动高度
+          const scrollContainer = this.$el.querySelector('.el-drawer__body');
+          const isAtBottom = this.isScrollAtBottom(scrollContainer);
+
+          this.file_list = this.cur_page === 1 ? resource_list : [...this.file_list, ...resource_list];
+          if (!resource_list || resource_list.length === 0) {
+            return;
+          }
+          this.cur_page += 1;
+
+          // 只有当前已经在底部时才微调滚动位置
+          if (isAtBottom) {
+            this.$nextTick(() => {
+              // 轻微向上滚动,创造滚动空间
+              scrollContainer.scrollTop -= 5;
+            });
+          }
+        })
+        .finally(() => {
+          this.loading = false;
+          this.lastLoadTime = now;
+        });
+    },
+    isScrollAtBottom(container) {
+      if (!container) return false;
+      return container.scrollHeight - container.scrollTop <= container.clientHeight + 5;
+    },
+    async handleFileClick(courseware_id, component_id) {
+      if (courseware_id) this.selectNode(courseware_id);
+      if (component_id) {
+        let node = await this.$refs.courserware.findChildComponentByKey(component_id);
+        if (node) {
+          await this.$nextTick();
+          this.$refs.previewMain.scrollTo({
+            top: node.offsetTop - 50,
+            left: node.offsetLeft - 50,
+            behavior: 'smooth',
+          });
+        }
+      }
+    },
+
+    /**
      * 文本转换
      * @param {string} text - 要转换的文本
      * @returns {string} - 转换后的文本
@@ -356,6 +683,12 @@ export default {
     toggleNavigationShow() {
       this.navigationShow = !this.navigationShow;
     },
+    /**
+     * 切换右侧工具栏显示与隐藏
+     */
+    toggleSidebarShow() {
+      this.sidebarShow = !this.sidebarShow;
+    },
 
     backTop() {
       this.$refs.previewMain.scrollTo({
@@ -367,7 +700,6 @@ export default {
   },
 };
 </script>
-
 <style lang="scss" scoped>
 @use '@/styles/mixin.scss' as *;
 
@@ -447,9 +779,12 @@ $total-width: $courseware-width + $courseware-left-margin + $courseware-right-ma
     min-width: 1110px;
     padding: 15px 0;
     overflow: auto;
-    background: url('@/assets/preview-bg.png') repeat;
+    background-color: #ececec;
 
     .catalogue-bar {
+      position: absolute;
+      top: 15px;
+      left: 0;
       display: flex;
       align-items: center;
       justify-content: center;
@@ -461,11 +796,47 @@ $total-width: $courseware-width + $courseware-left-margin + $courseware-right-ma
       border-radius: 2px;
       box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 40%);
     }
+
+    .sidebar-bar {
+      position: absolute;
+      top: 0;
+      right: 240px;
+      display: flex;
+      width: 60px;
+      height: calc(100vh - 166px);
+
+      .toolbar {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        width: 60px;
+        height: 100%;
+
+        img {
+          cursor: pointer;
+        }
+
+        &-special {
+          display: flex;
+          flex-direction: column;
+          row-gap: 16px;
+          align-items: center;
+          width: 100%;
+          margin-bottom: 24px;
+          background-color: #fff;
+
+          img {
+            width: 36px;
+            height: 36px;
+          }
+        }
+      }
+    }
   }
 
   .back-top {
     position: absolute;
-    right: 24px;
+    right: 240px;
     bottom: 0;
     display: flex;
     place-content: center center;
@@ -492,14 +863,14 @@ $total-width: $courseware-width + $courseware-left-margin + $courseware-right-ma
       width: $courseware-left-margin;
       min-width: $courseware-left-margin;
       max-width: $courseware-left-margin;
-      background-color: #f9f9f9;
+      background-color: $courseware-bgColor;
     }
 
     .preview-right {
       width: $courseware-right-margin;
       min-width: $courseware-right-margin;
       max-width: $courseware-right-margin;
-      background-color: #f9f9f9;
+      background-color: $courseware-bgColor;
     }
 
     &.no-audit {
@@ -528,13 +899,16 @@ $total-width: $courseware-width + $courseware-left-margin + $courseware-right-ma
         border-bottom: $border;
 
         .cover-image {
+          display: flex;
+          align-items: center;
+          justify-content: center;
           width: 111px;
           height: 157px;
           background-color: rgba(229, 229, 229, 100%);
 
           img {
-            width: 100%;
-            height: 100%;
+            max-width: 111px;
+            max-height: 157px;
           }
         }
 
@@ -559,6 +933,16 @@ $total-width: $courseware-width + $courseware-left-margin + $courseware-right-ma
             .name {
               font-weight: bold;
             }
+
+            .editor {
+              display: -webkit-box;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              word-break: break-word;
+              white-space: normal;
+              -webkit-line-clamp: 2; /* 多行省略行数,按需调整 */
+              -webkit-box-orient: vertical;
+            }
           }
         }
       }
@@ -611,6 +995,162 @@ $total-width: $courseware-width + $courseware-left-margin + $courseware-right-ma
         }
       }
     }
+
+    .sidebar {
+      position: relative;
+      display: flex;
+      width: $sidebar-width;
+
+      .toolbar {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        width: 60px;
+        height: 100%;
+        background-color: rgba(247, 248, 250, 100%);
+
+        img {
+          cursor: pointer;
+        }
+
+        &-special {
+          display: flex;
+          flex-direction: column;
+          row-gap: 16px;
+          margin-bottom: 24px;
+        }
+
+        &-list {
+          display: flex;
+          flex-direction: column;
+          row-gap: 16px;
+          align-items: center;
+          width: 100%;
+
+          .sidebar-item {
+            width: 100%;
+            text-align: center;
+
+            .sidebar-icon {
+              width: 36px;
+              height: 36px;
+              cursor: pointer;
+            }
+
+            &.active {
+              background-color: #4095e5;
+            }
+          }
+        }
+      }
+
+      .content {
+        flex: 1;
+        background-color: #fff;
+
+        :deep .el-drawer {
+          width: 240px !important;
+          border: 1px solid #e5e5e5;
+          transition: none !important;
+          animation: none !important;
+
+          .el-drawer__body {
+            height: calc(100vh - 200px);
+            overflow-y: auto;
+
+            h5 {
+              padding: 0 5px;
+              margin: 0;
+              font-size: 18px;
+              line-height: 40px;
+              background: #f2f3f5;
+            }
+
+            .scroll-container {
+              display: flex;
+              flex-direction: column;
+              row-gap: 8px;
+              margin: 6px;
+
+              .list-item {
+                display: flex;
+                align-items: center;
+                cursor: pointer;
+                border: 1px solid #ccc;
+                border-radius: 8px;
+
+                :deep .el-slider {
+                  .el-slider__runway {
+                    background-color: #eee;
+                  }
+                }
+
+                .el-image {
+                  display: flex;
+                  width: 100%;
+                  min-width: 100%;
+                  height: 90px;
+                  background-color: #ccc;
+                  border-radius: 8px;
+                }
+
+                .video-play {
+                  width: 100%;
+                  min-width: 100%;
+                }
+
+                .text-box {
+                  word-break: break-word;
+                }
+              }
+            }
+          }
+
+          p {
+            color: #999;
+            text-align: center;
+          }
+        }
+      }
+
+      .back-top {
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        display: flex;
+        place-content: center center;
+        align-items: center;
+        width: 60px;
+        height: 60px;
+        cursor: pointer;
+      }
+    }
+  }
+}
+
+:deep .scroll-container .audio-wrapper .audio-middle {
+  width: 210px !important;
+  padding: 6px 8px !important;
+  border: none;
+  border-radius: 8px;
+
+  .audio-name {
+    text-align: left;
+  }
+
+  .slider-area {
+    column-gap: 8px !important;
+  }
+}
+
+:deep .audit-dialog {
+  .el-dialog__body {
+    height: calc(100vh - 260px);
+    padding: 5px 20px;
+  }
+
+  .mind-map-container .mind-map {
+    height: calc(100vh - 310px);
   }
 }
 </style>