<!-- eslint-disable vue/multi-word-component-names -->
|
<template>
|
<div class="page-main" @scroll="throttledScrollHandler">
|
<div
|
class="public-books"
|
@mouseup="handleMouseUp"
|
:style="{
|
fontSize: fontSize ? fontSize + 'px' : '16px',
|
transform: `scale(${pageZoom ? pageZoom : 1})`,
|
}"
|
>
|
<front001 v-if="showCatalogList.indexOf(1) > -1" />
|
<ChapterOne v-if="showCatalogList.indexOf(2) > -1" />
|
<ChapterTwo v-if="showCatalogList.indexOf(3) > -1" />
|
<ChapterThree v-if="showCatalogList.indexOf(4) > -1" />
|
<ChapterFour v-if="showCatalogList.indexOf(5) > -1" />
|
<ChapterFive v-if="showCatalogList.indexOf(6) > -1" />
|
<chapterSix v-if="showCatalogList.indexOf(7) > -1" />
|
<chapterSeven v-if="showCatalogList.indexOf(8) > -1" />
|
<chapterEight v-if="showCatalogList.indexOf(9) > -1" />
|
<chapterNine v-if="showCatalogList.indexOf(10) > -1" />
|
<chapter010 v-if="showCatalogList.indexOf(11) > -1" />
|
<chapter011 v-if="showCatalogList.indexOf(12) > -1" />
|
<chapter012 v-if="showCatalogList.indexOf(13) > -1" />
|
<chapter013 v-if="showCatalogList.indexOf(14) > -1" />
|
<chapter014 v-if="showCatalogList.indexOf(15) > -1" />
|
<chapter015 v-if="showCatalogList.indexOf(16) > -1" />
|
<chapter016 v-if="showCatalogList.indexOf(17) > -1" />
|
<chapter017 v-if="showCatalogList.indexOf(18) > -1" />
|
<chapter018 v-if="showCatalogList.indexOf(19) > -1" />
|
</div>
|
</div>
|
</template>
|
|
<script>
|
import front001 from "./view/front001";
|
import ChapterOne from "./view/chapter001";
|
import ChapterTwo from "./view/chapter002";
|
import ChapterThree from "./view/chapter003";
|
import ChapterFour from "./view/chapter004";
|
import ChapterFive from "./view/chapter005";
|
import chapterSix from "./view/chapter006";
|
import chapterSeven from "./view/chapter007";
|
import chapterEight from "./view/chapter008";
|
import chapterNine from "./view/chapter009";
|
import chapter010 from "./view/chapter010";
|
import chapter011 from "./view/chapter011";
|
import chapter012 from "./view/chapter012";
|
import chapter013 from "./view/chapter013";
|
import chapter014 from "./view/chapter014";
|
import chapter015 from "./view/chapter015";
|
import chapter016 from "./view/chapter016";
|
import chapter017 from "./view/chapter017";
|
import chapter018 from "./view/chapter018";
|
import NoteIcon from "@/assets/images/biji.png";
|
import _ from "lodash";
|
export default {
|
data() {
|
return {
|
catalogLength: 18, // 总章节数
|
showCatalogList: [], // 显示的章节
|
loadThreshold: 300, // 触发加载阈值
|
throttleThreshold: 100, // 节流阈值
|
previousScrollTop: 0,
|
throttledScrollHandler: null,
|
observer: null,
|
};
|
},
|
watch: {
|
showCatalogList: {
|
// eslint-disable-next-line no-unused-vars
|
handler(newVal, oldVal) {
|
if (
|
this.$store.state.qiankun &&
|
this.$store.state.qiankun.catalogChange
|
) {
|
// 调用父层方法
|
this.$store.state.qiankun.catalogChange({
|
showCatalogList: newVal,
|
});
|
}
|
// 启动页码观察
|
setTimeout(() => {
|
this.initObservation();
|
this.initThemeColor();
|
}, 500);
|
},
|
},
|
},
|
mounted() {
|
// 默认加载章节
|
this.showCatalogList = [1];
|
// 滚动监听节流
|
this.throttledScrollHandler = _.throttle(
|
this.scrollFun,
|
this.throttleThreshold,
|
{ leading: true, trailing: false }
|
);
|
// 定义子层方法
|
if (this.setGlobalState) {
|
// 提供页面跳转功能
|
this.setGlobalState({
|
gotoPage: (catalog, page) => {
|
this.gotoPage(catalog, page);
|
},
|
// 渲染笔记、高亮、划线
|
renderSign: (type, data) => {
|
this.renderSign(type, data);
|
},
|
// 删除笔记、高亮、划线
|
delSign: (data) => {
|
this.delSign(data);
|
},
|
});
|
}
|
|
// 创建一个新的 Intersection Observer 实例,用于观察目标元素和执行相应的回调函数。
|
// new IntersectionObserver(callback, options):使用之前定义的 callback 回调函数和 options 配置选项来初始化 Intersection Observer 实例。
|
this.observer = new IntersectionObserver(this.pageChangeCallback, {
|
root: null, // 指定根元素,这里设为 null,表示选取整个视窗作为根元素。
|
rootMargin: "0px", // 指定根元素的边界,这里设为 "0px",表示根元素的边界和视窗的边界重合
|
threshold: 0.5, // 指定交叉比例,这里设为 0.5,表示当目标元素一半或更多显示在视窗中时触发回调函数。
|
});
|
|
// 启动页码观察
|
setTimeout(() => {
|
this.initObservation();
|
this.initThemeColor();
|
}, 500);
|
|
// 测试页面跳转
|
// setTimeout(() => {
|
// this.gotoPage(5, 100);
|
// setTimeout(() => {
|
// this.renderSign("Note", {
|
// id: "2ACA9359",
|
// txt: "营养素和热量,才能",
|
// page: "100",
|
// type: "Highlight",
|
// color: "#F5E12A"
|
// });
|
// setTimeout(() => {
|
// this.delSign({
|
// ids: ["2ACA9359"]
|
// });
|
// }, 2000);
|
// }, 1000);
|
// }, 3000);
|
},
|
components: {
|
front001,
|
ChapterOne,
|
ChapterTwo,
|
ChapterThree,
|
ChapterFour,
|
ChapterFive,
|
chapterSix,
|
chapterSeven,
|
chapterEight,
|
chapterNine,
|
chapter010,
|
chapter011,
|
chapter012,
|
chapter013,
|
chapter014,
|
chapter015,
|
chapter016,
|
chapter017,
|
chapter018,
|
},
|
computed: {
|
fontSize() {
|
return this.$store.state.qiankun.fontSize;
|
},
|
pageZoom() {
|
return this.$store.state.qiankun.scale / 100;
|
},
|
},
|
methods: {
|
getParentWithClass(element, className) {
|
while (element.parentElement) {
|
element = element.parentElement;
|
if (element.classList.contains(className)) {
|
return element;
|
}
|
}
|
},
|
handleMouseUp(e) {
|
const selection = (
|
this.container ? this.container : window
|
).getSelection();
|
const txt = selection.toString();
|
if (selection.type != "none" && txt) {
|
// eslint-disable-next-line no-unused-vars
|
let node = selection.anchorNode.parentNode;
|
let pageHtml = this.getParentWithClass(
|
selection.anchorNode,
|
"page-box"
|
);
|
let chapterDom = this.getParentWithClass(
|
selection.anchorNode,
|
"chapter"
|
);
|
let chapterNum;
|
if (chapterDom) chapterNum = chapterDom.getAttribute("num");
|
if (pageHtml) {
|
const page = pageHtml.getAttribute("page");
|
// 监听选中文本事件,并触发父层方法
|
if (this.$store.state.qiankun.windowSelection) {
|
this.$store.state.qiankun.windowSelection({
|
chapterNum,
|
txt,
|
page,
|
x: e.x,
|
y: e.y,
|
});
|
}
|
}
|
} else {
|
if (this.$store.state.qiankun.windowSelection) {
|
this.$store.state.qiankun.windowSelection({
|
chapterNum: "",
|
txt: "",
|
page: "",
|
x: e.x,
|
y: e.y,
|
});
|
}
|
}
|
},
|
|
// 滚动监听
|
scrollFun(event) {
|
// 判断向上滚动还是向下滚动
|
if (event.target.scrollTop > this.previousScrollTop) {
|
// 向下
|
const currentScrollTop =
|
event.target.scrollTop + event.target.offsetHeight;
|
|
if (
|
currentScrollTop >=
|
event.target.scrollHeight - this.loadThreshold
|
) {
|
// 到达阈值
|
if (
|
this.showCatalogList[this.showCatalogList.length - 1] <
|
this.catalogLength
|
) {
|
// 加载下一章
|
this.showCatalogList.push(
|
this.showCatalogList[this.showCatalogList.length - 1] + 1
|
);
|
if (this.showCatalogList.length > 3) {
|
// 超过三章隐藏顶部一章
|
this.showCatalogList.shift();
|
}
|
}
|
}
|
} else if (event.target.scrollTop < this.previousScrollTop) {
|
// 向上
|
const currentScrollTop = event.target.scrollTop;
|
if (currentScrollTop <= this.loadThreshold) {
|
// 到达阈值
|
if (this.showCatalogList[0] > 0) {
|
// 加载上一章
|
this.showCatalogList.unshift(this.showCatalogList[0] - 1);
|
if (this.showCatalogList.length > 3) {
|
// 超过三章隐藏底部一章
|
this.showCatalogList.pop();
|
}
|
}
|
}
|
}
|
// showCatalogList 当前显示的三个章节,watch监听传递给主应用
|
// 更新上一次滚动的位置
|
this.previousScrollTop = event.target.scrollTop;
|
},
|
// 章节、页面跳转
|
gotoPage(catalog, page) {
|
if (catalog >= 0 && catalog <= this.catalogLength) {
|
// 处理渲染章节
|
if (catalog == 0) {
|
this.showCatalogList = [0, 1];
|
} else if (catalog == this.catalogLength) {
|
this.showCatalogList = [
|
this.catalogLength - 2,
|
this.catalogLength - 1,
|
this.catalogLength,
|
];
|
} else {
|
this.showCatalogList = [catalog - 1, catalog, catalog + 1];
|
}
|
setTimeout(() => {
|
// 跳转页码
|
const pageDom = (
|
this.container ? this.container : document
|
).querySelector(`[page="${page}"]`);
|
if (pageDom) {
|
pageDom.scrollIntoView();
|
} else {
|
console.log("页码错误!");
|
}
|
}, 500);
|
} else {
|
console.log("章节错误!");
|
}
|
},
|
|
// 渲染标记
|
renderSign(type, data) {
|
const existence = (
|
this.container ? this.container : document
|
).querySelector(`[dataid="${data.id}"]`);
|
// 去重
|
if (!existence) {
|
const pageDom = (
|
this.container ? this.container : document
|
).querySelector(`[page="${data.page}"]`);
|
let reg = new RegExp(`${data.txt}`, "ig");
|
switch (type) {
|
case "Highlight":
|
// 高亮
|
pageDom.innerHTML = pageDom.innerHTML.replace(
|
reg,
|
`<span datatype="Highlight" dataid="${data.id}" style="background: ${data.color};" class="highLight" onclick="signClick('Highlight','${data.id}','${data.chapterNum}')">${data.txt}</span>`
|
);
|
break;
|
case "Dashing":
|
// 划线
|
pageDom.innerHTML = pageDom.innerHTML.replace(
|
reg,
|
`<span datatype="Dashing" dataid="${data.id}" style="text-decoration-color:${data.color};" class="underline" onclick="signClick('Dashing','${data.id}','${data.chapterNum}')">${data.txt}</span>`
|
);
|
break;
|
case "Note":
|
// 笔记
|
pageDom.innerHTML = pageDom.innerHTML.replace(
|
reg,
|
`<span datatype="Note" dataid="${data.id}" style="border-bottom-color:${data.color}" class="notesline" onclick="signClick('Note','${data.id}','${data.chapterNum}')" onmouseover="noteHover('Note','${data.id}','${data.chapterNum}')">${data.txt}<img src="${NoteIcon}" style="cursor: pointer" /></span>`
|
);
|
break;
|
}
|
}
|
},
|
// 删除标记渲染
|
delSign({ ids, type }) {
|
if (ids && ids.length) {
|
for (let i = 0; i < ids.length; i++) {
|
const id = ids[i];
|
const dom = (
|
this.container ? this.container : document
|
).querySelector(`[dataid="${id}"]`);
|
dom.parentNode.innerHTML = dom.parentNode.innerHTML.replace(
|
dom.outerHTML,
|
dom.outerText
|
);
|
}
|
}
|
if (type) {
|
const doms = (
|
this.container ? this.container : document
|
).querySelectorAll(`[datatype="${type}"]`);
|
for (let i = 0; i < doms.length; i++) {
|
const dom = doms[i];
|
dom.parentNode.innerHTML = dom.parentNode.innerHTML.replace(
|
dom.outerHTML,
|
dom.outerText
|
);
|
}
|
}
|
},
|
initObservation() {
|
const sections = (
|
this.container ? this.container : document
|
).querySelectorAll(".page-box");
|
sections.forEach((section) => {
|
//observer 观察每个元素,以便在它们进入或离开视窗时触发回调函数。
|
const isObserver = section.getAttribute("observer");
|
if (!isObserver) {
|
this.observer.observe(section);
|
section.setAttribute("observer", "1");
|
}
|
});
|
},
|
initThemeColor() {
|
// 获取各种需要主题色的节点
|
const colorDom = (
|
this.container ? this.container : document
|
).querySelectorAll(".theme-color");
|
const backgroundColorDom = (
|
this.container ? this.container : document
|
).querySelectorAll(".theme-back");
|
const borderColorDom = (
|
this.container ? this.container : document
|
).querySelectorAll(".theme-border");
|
// 获取配置的主题色
|
const bookThemeColor =
|
this.config.activeBook && this.config.activeBook.bookThemeColor
|
? this.config.activeBook.bookThemeColor
|
: null;
|
const chapterThemeColor =
|
this.config.activeBook && this.config.activeBook.chapterThemeColor
|
? this.config.activeBook.chapterThemeColor
|
: null;
|
const pageThemeColor =
|
this.config.activeBook && this.config.activeBook.pageThemeColor
|
? this.config.activeBook.pageThemeColor
|
: null;
|
colorDom.forEach((domItem) => {
|
// 获取章节、页码
|
let pageDom = this.getParentWithClass(domItem, "page-box");
|
let chapterDom = this.getParentWithClass(domItem, "chapter");
|
let page, chapterNum;
|
if (pageDom) page = pageDom.getAttribute("page");
|
if (chapterDom) chapterNum = chapterDom.getAttribute("num");
|
// 向上匹配主题色
|
const themeColor =
|
page && pageThemeColor && pageThemeColor[page]
|
? pageThemeColor[page]
|
: chapterNum && chapterThemeColor && chapterThemeColor[chapterNum]
|
? chapterThemeColor[chapterNum]
|
: bookThemeColor;
|
if (themeColor) {
|
domItem.style.color = themeColor;
|
}
|
});
|
backgroundColorDom.forEach((domItem) => {
|
// 获取章节、页码
|
let pageDom = this.getParentWithClass(domItem, "page-box");
|
let chapterDom = this.getParentWithClass(domItem, "chapter");
|
let page, chapterNum;
|
if (pageDom) page = pageDom.getAttribute("page");
|
if (chapterDom) chapterNum = chapterDom.getAttribute("num");
|
// 向上匹配主题色
|
const themeColor =
|
page && pageThemeColor && pageThemeColor[page]
|
? pageThemeColor[page]
|
: chapterNum && chapterThemeColor && chapterThemeColor[chapterNum]
|
? chapterThemeColor[chapterNum]
|
: bookThemeColor;
|
if (themeColor) {
|
domItem.style.backgroundColor = themeColor;
|
}
|
});
|
borderColorDom.forEach((domItem) => {
|
// 获取章节、页码
|
let pageDom = this.getParentWithClass(domItem, "page-box");
|
let chapterDom = this.getParentWithClass(domItem, "chapter");
|
let page, chapterNum;
|
if (pageDom) page = pageDom.getAttribute("page");
|
if (chapterDom) chapterNum = chapterDom.getAttribute("num");
|
// 向上匹配主题色
|
const themeColor =
|
page && pageThemeColor && pageThemeColor[page]
|
? pageThemeColor[page]
|
: chapterNum && chapterThemeColor && chapterThemeColor[chapterNum]
|
? chapterThemeColor[chapterNum]
|
: bookThemeColor;
|
if (themeColor) {
|
domItem.style.borderColor = themeColor;
|
}
|
});
|
},
|
// eslint-disable-next-line
|
getParentWithClass(element, className) {
|
while (element.parentElement) {
|
element = element.parentElement;
|
if (element.classList.contains(className)) {
|
return element;
|
}
|
}
|
},
|
// eslint-disable-next-line no-unused-vars
|
pageChangeCallback(entries, observer) {
|
//entries:代表观察到的目标元素的集合。 observer:代表观察者对象。
|
entries.forEach((entry) => {
|
//entry.isIntersecting:检查当前目标元素是否与根元素相交。
|
if (entry.isIntersecting) {
|
const target = entry.target;
|
//entry.target:获取当前目标元素
|
const page = target.getAttribute("page");
|
const catalogDom = this.tool.getParentNodeByClassName(
|
target,
|
"chapter"
|
);
|
const catalog = catalogDom.getAttribute("num");
|
// 返回页码和章节信息
|
if (this.$store.state.qiankun && this.$store.state.qiankun.pageChange)
|
this.$store.state.qiankun.pageChange({
|
page: page,
|
catalog: catalog,
|
});
|
// const sections = Array.from(document.querySelectorAll(".section"));
|
//sections:获取所有具有 .section 类名的元素,并转换为数组。
|
// let index = sections.findIndex((section) => section === target) + 1;
|
//index:查找当前目标元素在 sections 数组中的索引,并加 1,用于确定当前页码。
|
}
|
});
|
},
|
}
|
};
|
</script>
|
|
<style lang="less">
|
@import "./css/default.less";
|
</style>
|