<template>
|
<div class="page-main" @scroll="throttledScrollHandler">
|
<div class="page-content">
|
<pageHeader ></pageHeader>
|
<chapterOne @saveCharacters="saveCharacters"></chapterOne>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
import pageHeader from './header.vue'
|
import chapterOne from './chapter001.vue'
|
export default {
|
name: "pageContent",
|
components:{pageHeader,chapterOne},
|
data() {
|
return {
|
catalogLength: 2, // 总章节数
|
showCatalogList: [], // 显示的章节
|
loadThreshold: 300, // 触发加载阈值
|
throttleThreshold: 100, // 节流阈值
|
previousScrollTop: 0,
|
throttledScrollHandler: null,
|
observer: null,
|
};
|
},
|
watch: {
|
showCatalogList: {
|
handler(newVal, oldVal) {
|
if (
|
this.$store.state.qiankun &&
|
this.$store.state.qiankun.catalogChange
|
) {
|
// 调用父层方法
|
this.$store.state.qiankun.catalogChange({
|
showCatalogList: newVal,
|
});
|
}
|
// 启动页码观察
|
setTimeout(() => {
|
this.initObservation();
|
}, 500);
|
},
|
},
|
},
|
mounted() {
|
// 默认加载章节
|
this.showCatalogList = [0, 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();
|
}, 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);
|
},
|
methods: {
|
// 滚动监听
|
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");
|
//observer 观察每个元素,以便在它们进入或离开视窗时触发回调函数。
|
sections.forEach((section) => {
|
const isObserver = section.getAttribute("observer");
|
if (!isObserver) {
|
this.observer.observe(section);
|
section.setAttribute("observer", "1");
|
}
|
});
|
},
|
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");
|
console.log("page", page, catalog);
|
// 返回页码和章节信息
|
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,用于确定当前页码。
|
}
|
});
|
},
|
// 点击事件:将生僻单词传给 主应用 从而调用词典功能
|
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
|
});
|
}
|
// chooseWords
|
}
|
},
|
};
|
</script>
|
|
<style lang="less" scoped>
|
.page-main {
|
width: 100%;
|
height: 100%;
|
overflow: auto;
|
.page-content {
|
max-width: 816px;
|
min-width: 375px;
|
margin: 0 auto;
|
padding-bottom: 100px;
|
}
|
}
|
</style>
|