user1
2024-06-28 9258654657affed07bbee9f999d1bf31ef9e6e20
src/books/English/view/components/index.vue
@@ -1,29 +1,44 @@
<template>
  <div class="page-main" @scroll="throttledScrollHandler">
    <div
      class="page-content"
      :style="{
        fontSize: fontSize ? fontSize + 'px' : '16px',
        transform: `scale(${pageZoom ? pageZoom : 1})`,
        transformOrigin: 'center top',
      }"
    >
      <pageHeader
        v-if="showCatalogList.indexOf(1) > -1"
        :showPageList="loadPageList"
      ></pageHeader>
      <chapterOne
        @saveCharacters="saveCharacters"
        v-if="showCatalogList.indexOf(2) > -1"
        :showPageList="loadPageList"
      ></chapterOne>
    <div class="page-content" :style="{
      fontSize: fontSize ? fontSize + 'px' : '16px',
      transform: `scale(${pageZoom ? pageZoom : 1})`,
      transformOrigin: 'center top',
    }">
      <pageHeader v-if="showCatalogList.indexOf(1) > -1" :showPageList="loadPageList"></pageHeader>
      <chapterOne @saveCharacters="saveCharacters" @closeMiniAudio="closeMiniAudio"
        v-if="showCatalogList.indexOf(2) > -1" :showPageList="loadPageList"></chapterOne>
      <chapterTwo @saveCharacters="saveCharacters" @closeMiniAudio="closeMiniAudio"
        v-if="showCatalogList.indexOf(3) > -1" :showPageList="loadPageList"></chapterTwo>
        <chapterThree @saveCharacters="saveCharacters" @closeMiniAudio="closeMiniAudio"
        v-if="showCatalogList.indexOf(4) > -1" :showPageList="loadPageList"></chapterThree>
        <chapterFour @saveCharacters="saveCharacters" @closeMiniAudio="closeMiniAudio"
        v-if="showCatalogList.indexOf(5) > -1" :showPageList="loadPageList"></chapterFour>
        <chapterFive @saveCharacters="saveCharacters" @closeMiniAudio="closeMiniAudio"
        v-if="showCatalogList.indexOf(6) > -1" :showPageList="loadPageList"></chapterFive>
        <chapterSex @saveCharacters="saveCharacters" @closeMiniAudio="closeMiniAudio"
        v-if="showCatalogList.indexOf(7) > -1" :showPageList="loadPageList"></chapterSex>
        <chapterSeven @saveCharacters="saveCharacters" @closeMiniAudio="closeMiniAudio"
        v-if="showCatalogList.indexOf(8) > -1" :showPageList="loadPageList"></chapterSeven>
        <chapterEight @saveCharacters="saveCharacters" @closeMiniAudio="closeMiniAudio"
        v-if="showCatalogList.indexOf(9) > -1" :showPageList="loadPageList"></chapterEight>
    </div>
    <miniAudio :path="audioPath" :currentTime="currentTime" @closeMiniAudio="closeMiniAudio" ref="audioPlayer">
    </miniAudio>
  </div>
</template>
<script>
import pageHeader from "./header.vue";
import chapterOne from "./chapter001.vue";
import chapterTwo from "./chapter002.vue";
import chapterThree from "./chapter003.vue";
import chapterFour from "./chapter004.vue";
import chapterFive from "./chapter005.vue";
import chapterSex from "./chapter006.vue";
import chapterSeven from "./chapter007.vue";
import chapterEight from "./chapter008.vue";
import miniAudio from "@/components/miniAudio/index.vue";
import NoteIcon from "@/assets/images/biji.png";
import _ from "lodash";
import Swiper from "swiper/bundle";
@@ -33,7 +48,7 @@
export default {
  data() {
    return {
      catalogLength: 2, // 总章节数
      catalogLength: 9, // 总章节数
      showCatalogList: [], // 显示的章节
      loadThreshold: 300, // 触发加载阈值
      throttleThreshold: 100, // 节流阈值
@@ -42,8 +57,12 @@
      observer: null,
      loadPageObserver: null,
      loadPageList: [],
      questionData: {},
      questionDataMap: {},
      renderSignMap: {},
      highlightData: null,
      audioPath: "",
      currentTime: null,
      videoList: [],
    };
  },
  computed: {
@@ -75,11 +94,18 @@
    },
    loadPageList: {
      handler(newVal, oldVal) {
        console.log(newVal, "this.loadPageList");
        setTimeout(() => {
          this.initSwiper();
          this.initViewer();
        }, 200);
      },
    },
    pageZoom: {
      handler(newVal, oldVal) {
        const scrollBox = (
          this.container ? this.container : document
        ).querySelector(".page-main");
        scrollBox.scrollTop = (scrollBox.scrollTop / oldVal) * newVal;
      },
    },
  },
@@ -109,6 +135,14 @@
        delSign: (data) => {
          this.delSign(data);
        },
        // 全文检索
        searchBookByKeyword: (keyword) => {
          return this.searchTextByPage(keyword);
        },
        // 跳转检索结果位置
        jumpSearchItem: (data) => {
          this.searchItemLocation(data);
        },
      });
    }
@@ -133,29 +167,57 @@
    }, 500);
    // 测试页面跳转
    // setTimeout(() => {
    //   this.gotoPage(1, 10);
    //   setTimeout(() => {
    //     this.renderSign("Highlight", {
    //       id: "2ACA9359",
    //       txt: "题一学习主题一 运动",
    //       page: "10",
    //       type: "Highlight",
    //       color: "#F5E12A"
    //     });
    // setTimeout(() => {
    //   this.delSign({
    //     ids: ["2ACA9359"]
    //   });
    // }, 2000);
    //   }, 5000);
    // }, 1000);
     setTimeout(() => {
       this.gotoPage(2,7);
      //   setTimeout(() => {
      //     this.renderSign("Highlight", {
      //       id: "2ACA9359",
      //       txt: "题一学习主题一 运动",
      //       page: "10",
      //       type: "Highlight",
      //       color: "#F5E12A"
      //     });
      // setTimeout(() => {
      //   this.delSign({
      //     ids: ["2ACA9359"]
      //   });
      // }, 2000);
      //   }, 5000);
      // const pageDom = (this.container ? this.container : document)
      //   .querySelector("#app")
      //   .querySelectorAll(".page-box");
      // 检索
      // console.log(this.searchTextByPage("保护内脏器官"), "searchTextByPage");
      // 检索跳转
      // this.searchItemLocation({
      //   catalog: 2,
      //   page: 10,
      //   txt: " 运动系统是由骨、骨连结和骨骼肌三部分组成的。全身的骨通过骨连结组成人体骨骼(见图1-1)。骨骼是人体的支架,具有保护内脏器官、供肌肉附着和作为肌肉运动的杠杆等作用。在神经系统的支配下,肌肉收缩牵动所附着的骨绕着关节转动,使身体产生各种动作。所以,运动系统具有运动、支持和保护等功能,幼年时期的骨骼还具有造血功能。 ",
      //   txtIndex: 57
      // });
     }, 500);
  },
  methods: {
    // setZoom1() {
    //   let scale = this.$store.state.qiankun.scale + 10;
    //   const scrollBox = (
    //     this.container ? this.container : document
    //   ).querySelector(".page-main");
    //   this.$store.commit("setZoom", scale);
    // },
    // setZoom2() {
    //   let scale = this.$store.state.qiankun.scale - 10;
    //   const scrollBox = (
    //     this.container ? this.container : document
    //   ).querySelector(".page-main");
    //   this.$store.commit("setZoom", scale);
    // },
    // 滚动监听
    scrollFun(event) {
      // 判断向上滚动还是向下滚动
      if (event.target.scrollTop > this.previousScrollTop) {
        this.getAduio();
        // 向下
        const currentScrollTop =
          event.target.scrollTop + event.target.offsetHeight;
@@ -179,6 +241,7 @@
          }
        }
      } else if (event.target.scrollTop < this.previousScrollTop) {
        this.handleAudio();
        // 向上
        const currentScrollTop = event.target.scrollTop;
        if (currentScrollTop <= this.loadThreshold) {
@@ -230,6 +293,12 @@
    // 处理标记数据
    handelSignData(type, data) {
      if (this.loadPageList.indexOf(Number(data.page)) > -1) {
        // 立即渲染
        this.renderSign(type, data);
      }
      // 储存数据
      if (!this.renderSignMap[type]) this.renderSignMap[type] = {};
      if (!this.renderSignMap[type][data.page])
        this.renderSignMap[type][data.page] = [];
@@ -238,6 +307,10 @@
    // 渲染标记
    renderSign(type, data) {
      // 父层设置禁止渲染标记时不再进行渲染
      if (this.$store.state.qiankun.disableSign) {
        return false;
      }
      const existence = (
        this.container ? this.container : document
      ).querySelector(`[dataid="${data.id}"]`);
@@ -382,8 +455,8 @@
          page && pageThemeColor && pageThemeColor[page]
            ? pageThemeColor[page]
            : chapterNum && chapterThemeColor && chapterThemeColor[chapterNum]
            ? chapterThemeColor[chapterNum]
            : bookThemeColor;
              ? chapterThemeColor[chapterNum]
              : bookThemeColor;
        if (themeColor) {
          domItem.style.color = themeColor;
        }
@@ -400,8 +473,8 @@
          page && pageThemeColor && pageThemeColor[page]
            ? pageThemeColor[page]
            : chapterNum && chapterThemeColor && chapterThemeColor[chapterNum]
            ? chapterThemeColor[chapterNum]
            : bookThemeColor;
              ? chapterThemeColor[chapterNum]
              : bookThemeColor;
        if (themeColor) {
          domItem.style.backgroundColor = themeColor;
        }
@@ -418,14 +491,15 @@
          page && pageThemeColor && pageThemeColor[page]
            ? pageThemeColor[page]
            : chapterNum && chapterThemeColor && chapterThemeColor[chapterNum]
            ? chapterThemeColor[chapterNum]
            : bookThemeColor;
              ? chapterThemeColor[chapterNum]
              : bookThemeColor;
        if (themeColor) {
          domItem.style.borderColor = themeColor;
        }
      });
    },
    getParentWithClass(element, className) {
      console.log(element, className, "element, className");
      while (element.parentElement) {
        element = element.parentElement;
        if (element.classList.contains(className)) {
@@ -448,7 +522,7 @@
          const catalog = catalogDom.getAttribute("num");
          let text = null;
          if (target.querySelector("p")) {
            text = target.querySelector("p").textContent.substring(0, 20);
            text = target.querySelector("p").textContent.substring(0, 50);
          }
          // 返回页码和章节信息
          if (this.$store.state.qiankun && this.$store.state.qiankun.pageChange)
@@ -474,17 +548,18 @@
              target,
              "chapter"
            );
            // 添加页码
            this.loadPageList.push(Number(page));
            const catalog = catalogDom.getAttribute("num");
            // if (!this.questionData[page]) {
            // if (!this.questionDataMap[page]) {
            //   if (testData && testData[catalog]) {
            //     if (testData[catalog][page]) {
            //       if (Array.isArray(testData[catalog][page])) {
            //         this.questionData[page] = await getQuestionList(
            //         this.questionDataMap[page] = await getQuestionList(
            //           page,
            //           testData[catalog][page],
            //           this.config.activeBook
            //         );
            //       } else {
            //         const obj = {};
            //         for (let key in testData[catalog][page]) {
@@ -494,14 +569,12 @@
            //             this.config.activeBook
            //           );
            //         }
            //         this.questionData[page] = obj;
            //         this.questionDataMap[page] = obj;
            //       }
            //       console.log('题目',this.questionData);
            //       console.log("题目", this.questionDataMap);
            //     }
            //   }
            // }
            // 添加页码
            this.loadPageList.push(Number(page));
            // 渲染这一页的标记
            for (const key in this.renderSignMap) {
              if (this.renderSignMap[key][page]) {
@@ -509,6 +582,39 @@
                  this.renderSign(key, item);
                });
              }
            }
            // 处理高亮
            if (this.highlightData) {
              // 高亮行
              setTimeout(() => {
                // 获取页面所有text节点
                const pageTextList = document.createTreeWalker(
                  target,
                  NodeFilter.SHOW_TEXT
                );
                // 匹配关键字
                const allPageTextNodes = [];
                let currentNode = pageTextList.nextNode();
                while (currentNode) {
                  allPageTextNodes.push(currentNode);
                  currentNode = pageTextList.nextNode();
                }
                for (let i = 0; i < allPageTextNodes.length; i++) {
                  const textDom = allPageTextNodes[i];
                  let txtIndex = textDom.textContent.indexOf(
                    this.highlightData.txt
                  );
                  if (txtIndex > -1) {
                    textDom.parentNode.style.transition =
                      "background-color 0.8s";
                    textDom.parentNode.scrollIntoView();
                    textDom.parentNode.style.backgroundColor = "#79bbf0";
                    setTimeout(() => {
                      textDom.parentNode.style.backgroundColor = "";
                    }, 1000);
                  }
                }
              }, 100);
            }
            if (this.loadPageList.length > 5) {
              // 超过5页
@@ -521,7 +627,7 @@
    initSwiper() {
      const doms = (
        this.container ? this.container : document
      ).querySelectorAll(".swiper-container");
      ).querySelectorAll(".swiper-img");
      for (let i = 0; i < doms.length; i++) {
        const dom = doms[i];
        new Swiper(dom, {
@@ -529,23 +635,23 @@
          autoplay: {
            //自动开始
            delay: 3000, //时间间隔
            disableOnInteraction: false, //*手动操作轮播图后不会暂停*
            disableOnInteraction: false //*手动操作轮播图后不会暂停*
          },
          paginationClickable: true,
          slidesPerView: 1, // 一组三个
          spaceBetween: 30, // 间隔
          // 如果需要前进后退按钮
          navigation: {
            nextEl: (this.container ? this.container : document).querySelector(
            nextEl: dom.querySelector(
              ".swiper-button-next"
            ),
            prevEl: (this.container ? this.container : document).querySelector(
            prevEl: dom.querySelector(
              ".swiper-button-prev"
            ),
            )
          },
          // 窗口变化,重新init,针对F11全屏和放大缩小,必须加
          observer: true,
          observeParents: true,
          observeParents: true
          // // 如果需要分页器
          // pagination: {
          //   el: (this.container ? this.container : document).querySelector(
@@ -553,6 +659,47 @@
          //   ),
          //   clickable: true // 分页器可以点击
          // }
        });
      }
      const pptDoms = (
        this.container ? this.container : document
      ).querySelectorAll(".swiper_ppt");
      for (let i = 0; i < pptDoms.length; i++) {
        const dom = pptDoms[i];
        new Swiper(dom, {
          loop: false, // 无缝
          autoplay: false,
          paginationClickable: true,
          slidesPerView: 1, // 一组三个
          spaceBetween: 30, // 间隔
          // 如果需要前进后退按钮
          navigation: {
            nextEl: dom.querySelector(
              ".swiper-button-next"
            ),
            prevEl: dom.querySelector(
              ".swiper-button-prev"
            )
          },
          // 窗口变化,重新init,针对F11全屏和放大缩小,必须加
          observer: true,
          observeParents: true,
          on: {
            init: (value) => {
              let currentPage = value.activeIndex + 1; // 获取当前页(从1开始计数)
              let totalPages = value.slides.length; // 获取总页数
              var paginationInfoEl = dom.querySelector(".pageBox");
              if (paginationInfoEl)
                paginationInfoEl.textContent = currentPage + "/" + totalPages;
            },
            slideChange: (value) => {
              let currentPage = value.activeIndex + 1; // 获取当前页(从1开始计数)
              let totalPages = value.slides.length; // 获取总页数
              var paginationInfoEl = dom.querySelector(".pageBox");
              if (paginationInfoEl)
                paginationInfoEl.textContent = currentPage + "/" + totalPages;
            }
          }
        });
      }
    },
@@ -572,23 +719,197 @@
        });
      }
    },
    // 点击事件:将生僻单词传给 主应用 从而调用词典功能
    saveCharacters(event,word) {
      console.log('生僻字',event,word);
      if (this.$store.state.qiankun && this.$store.state.qiankun.chooseWords) {
          this.$store.state.qiankun.chooseWords({
            word,
            x: event.pageX,
            y: event.pageY
          });
    // 根据关键字全文检索
    searchTextByPage(keyword) {
      const searchResult = [];
      let catalogIndex = 0;
      // 所有章节组件(每本书制作时单独配置)
      const pageData = {
        pageHeader,
        chapterOne,
        chapterTwo,
        chapterThree,
        chapterFour,
        chapterFive,
        chapterSex,
        chapterSeven,
        chapterEight,
      };
      // 遍历所有章节文件
      for (const key in pageData) {
        catalogIndex++;
        let pageComponent, pageExample;
        // 先渲染一次当前章节文件(这时页面的内容为空),获取页码信息
        pageComponent = Vue.extend(pageData[key]);
        pageExample = new pageComponent({
          propsData: {
            showPageList: [],
            questionData: {},
            isSearch: true
          },
        });
        pageExample.$mount(
          (this.container ? this.container : document).querySelector(
            "#searchContent"
          )
        );
        // 获取页码
        const pageDom = (this.container ? this.container : document)
          .querySelector("#searchDomBox")
          .querySelectorAll(".page-box");
        const pages = [];
        for (let i = 0; i < pageDom.length; i++) {
          const pageDomItem = pageDom[i];
          pages.push(Number(pageDomItem.getAttribute("page")));
        }
        // 获取页面结束,卸载销毁
        pageExample.$destroy();
        (this.container ? this.container : document).querySelector(
          "#searchDomBox"
        ).innerHTML = '<div id="searchContent"></div>';
        // 遍历页码
        if (pages.length) {
          for (let i = 0; i < pages.length; i++) {
            const pageNum = pages[i];
            // 动态渲染对应章节的页码
            pageComponent = Vue.extend(pageData[key]);
            pageExample = new pageComponent({
              propsData: {
                showPageList: [pageNum],
                questionData: {},
                isSearch: true
              },
            });
            pageExample.$mount(
              (this.container ? this.container : document).querySelector(
                "#searchContent"
              )
            );
            // 获取对应页面dom
            const thisPageDom = (this.container ? this.container : document)
              .querySelector("#searchDomBox")
              .querySelector(`[page="${pageNum}"]`);
            if (thisPageDom) {
              // 获取页面所有text节点
              const pageTextList = document.createTreeWalker(
                thisPageDom,
                NodeFilter.SHOW_TEXT
              );
              // 匹配关键字
              const allPageTextNodes = [];
              let currentNode = pageTextList.nextNode();
              while (currentNode) {
                allPageTextNodes.push(currentNode);
                currentNode = pageTextList.nextNode();
              }
              for (let i = 0; i < allPageTextNodes.length; i++) {
                const textDom = allPageTextNodes[i];
                let txtIndex = textDom.textContent.indexOf(keyword);
                if (txtIndex > -1) {
                  // 记录关键字所在页码、章节以及匹配到的段落
                  searchResult.push({
                    page: pageNum,
                    catalog: catalogIndex,
                    txt: textDom.textContent,
                    txtIndex: txtIndex,
                  });
                }
              }
              // 结束,卸载销毁
              pageExample.$destroy();
              (this.container ? this.container : document).querySelector(
                "#searchDomBox"
              ).innerHTML = '<div id="searchContent"></div>';
            }
          }
        }
      }
      // 输出搜索结果
      console.log(searchResult);
      return searchResult;
    },
    // 根据检索结果跳转对应位置并高亮
    searchItemLocation(data) {
      // 记录高亮信息
      this.highlightData = data;
      // 跳转
      this.gotoPage(data.catalog, data.page, () => { });
    },
    // 点击事件:将生僻单词传给 主应用 从而调用词典功能
    saveCharacters(event, word) {
      console.log("生僻字", event, word);
      if (this.$store.state.qiankun && this.$store.state.qiankun.chooseWords) {
        this.$store.state.qiankun.chooseWords({
          type: "word",
          word,
          x: event.pageX,
          y: event.pageY,
        });
      }
      // chooseWords
    }
    },
    // 页面向下滚动,音频小窗播放功能
    getAduio() {
      let allVideo = (
        this.container ? this.container : document
      ).querySelectorAll(".audio");
      allVideo = Array.from(allVideo);
      this.videoList = allVideo;
      if (allVideo.length) {
        // 查找播放状态的最后一条音频
        const playAudio = allVideo
          .reverse()
          .find((item) => item.paused == false);
        if (playAudio) {
          const bottomGap = playAudio.getBoundingClientRect().bottom;
          if (bottomGap < 0) {
            playAudio.pause();
            this.audioPath = playAudio.src;
            this.currentTime = playAudio.currentTime;
          }
        }
      }
    },
    // 页面向上滚动,音频小窗回收
    handleAudio() {
      if (!this.audioPath) return false;
      let allVideo = (
        this.container ? this.container : document
      ).querySelectorAll(".audio");
      allVideo = Array.from(allVideo);
      if (allVideo.length) {
        //查找与小窗播放音频同源的页面audio DOM
        const playAudio = allVideo.find((item) => item.src == this.audioPath);
        if (playAudio) {
          const bottomGap = playAudio.getBoundingClientRect().bottom;
          if (bottomGap >= 0) {
            if (this.$refs.audioPlayer) {
              const playerState = this.$refs.audioPlayer.getVideoPlayer();
              this.audioPath = "";
              playAudio.currentTime = playerState.currentTime;
              if (!playerState.paused) playAudio.play();
            }
          }
        }
      }
    },
    // 关闭mini video
    closeMiniAudio() {
      this.audioPath = "";
    },
  },
  components: {
    pageHeader,
    chapterOne,
    chapterTwo,
    chapterThree,
    chapterFour,
    chapterFive,
    chapterSex,
    chapterSeven,
    chapterEight,
    miniAudio,
  },
};
</script>
@@ -598,6 +919,7 @@
  width: 100%;
  height: 100%;
  overflow: auto;
  .page-content {
    max-width: 816px;
    min-width: 375px;