[skip ci] web: 重构章节内容及其进度
This commit is contained in:
@@ -88,11 +88,14 @@
|
||||
ref="chapter"
|
||||
>
|
||||
<chapter-content
|
||||
ref="chapterRef"
|
||||
:chapterIndex="data.index"
|
||||
:contents="data.content"
|
||||
:title="data.title"
|
||||
:spacing="store.config.spacing"
|
||||
:fontSize="fontSize"
|
||||
:fontFamily="fontFamily"
|
||||
@readedLengthChange="onReadedLengthChange"
|
||||
v-if="showContent"
|
||||
/>
|
||||
</div>
|
||||
@@ -109,10 +112,10 @@ import settings from "@/plugins/config";
|
||||
import API from "@api";
|
||||
import loadingSvg from "@element-plus/icons-svg/loading.svg?raw";
|
||||
|
||||
// loading spinner
|
||||
const showLoading = ref(false);
|
||||
const loadingSerive = ref(null);
|
||||
const content = ref();
|
||||
|
||||
watch(showLoading, (loading) => {
|
||||
if (!loading) return loadingSerive.value?.close();
|
||||
loadingSerive.value = ElLoading.service({
|
||||
@@ -132,13 +135,6 @@ try {
|
||||
localStorage.removeItem("config");
|
||||
}
|
||||
|
||||
const loading = ref();
|
||||
const noPoint = ref(true);
|
||||
const showToolBar = ref(false);
|
||||
const chapterData = ref([]);
|
||||
const scrollObserve = ref(null);
|
||||
const readingObserve = ref(null);
|
||||
|
||||
const {
|
||||
catalog,
|
||||
popCataVisible,
|
||||
@@ -147,6 +143,7 @@ const {
|
||||
showContent,
|
||||
config,
|
||||
readingBook,
|
||||
bookProgress,
|
||||
} = storeToRefs(store);
|
||||
const chapterPos = computed({
|
||||
get: () => readingBook.value.chapterPos,
|
||||
@@ -201,6 +198,7 @@ const chapterTheme = computed(() => {
|
||||
width: readWidth.value,
|
||||
};
|
||||
});
|
||||
const showToolBar = ref(false);
|
||||
const leftBarTheme = computed(() => {
|
||||
return {
|
||||
background: popupColor.value,
|
||||
@@ -221,28 +219,25 @@ const rightBarTheme = computed(() => {
|
||||
});
|
||||
const isNight = computed(() => theme.value == 6);
|
||||
|
||||
watchEffect(() => {
|
||||
if (chapterData.value.length > 0) {
|
||||
store.setContentLoading(false);
|
||||
//添加章节内容到observe
|
||||
addReadingObserve();
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
document.title = catalog.value[chapterIndex.value]?.title || document.title;
|
||||
store.saveBookProcess();
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (!infiniteLoading.value) {
|
||||
scrollObserve.value?.disconnect();
|
||||
} else {
|
||||
scrollObserve.value?.observe(loading.value);
|
||||
}
|
||||
});
|
||||
|
||||
// 顶部底部跳转
|
||||
const top = ref();
|
||||
const bottom = ref();
|
||||
const toTop = () => {
|
||||
jump(top.value);
|
||||
};
|
||||
const toBottom = () => {
|
||||
jump(bottom.value);
|
||||
};
|
||||
|
||||
// 书架路由切换
|
||||
const router = useRouter();
|
||||
const toShelf = () => {
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
// 获取章节内容
|
||||
const chapterData = ref([]);
|
||||
const noPoint = ref(true);
|
||||
const getContent = (index, reloadChapter = true, chapterPos = 0) => {
|
||||
if (reloadChapter) {
|
||||
//展示进度条
|
||||
@@ -252,6 +247,7 @@ const getContent = (index, reloadChapter = true, chapterPos = 0) => {
|
||||
jump(top.value, { duration: 0 });
|
||||
//从目录,按钮切换章节时保存进度 预加载时不保存
|
||||
saveReadingBookProgressToBrowser(index, chapterPos);
|
||||
chapterData.value = [];
|
||||
}
|
||||
let bookUrl = sessionStorage.getItem("bookUrl");
|
||||
let { title, index: chapterIndex } = catalog.value[index];
|
||||
@@ -261,12 +257,12 @@ const getContent = (index, reloadChapter = true, chapterPos = 0) => {
|
||||
if (res.data.isSuccess) {
|
||||
let data = res.data.data;
|
||||
let content = data.split(/\n+/);
|
||||
updateChapterData({ index, content, title }, reloadChapter);
|
||||
chapterData.value.push({ index, content, title });
|
||||
if (reloadChapter) toChapterPos(chapterPos);
|
||||
} else {
|
||||
ElMessage({ message: res.data.errorMsg, type: "error" });
|
||||
let content = [res.data.errorMsg];
|
||||
updateChapterData({ index, content, title }, reloadChapter);
|
||||
chapterData.value.push({ index, content, title });
|
||||
}
|
||||
store.setContentLoading(true);
|
||||
showLoading.value = false;
|
||||
@@ -279,55 +275,69 @@ const getContent = (index, reloadChapter = true, chapterPos = 0) => {
|
||||
(err) => {
|
||||
ElMessage({ message: "获取章节内容失败", type: "error" });
|
||||
let content = ["获取章节内容失败!"];
|
||||
updateChapterData({ index, content, title }, reloadChapter);
|
||||
chapterData.value.push({ index, content, title });
|
||||
showLoading.value = false;
|
||||
store.setShowContent(true);
|
||||
throw err;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// 章节进度跳转和计算
|
||||
const chapter = ref();
|
||||
const chapterRef = ref();
|
||||
const toChapterPos = (pos) => {
|
||||
nextTick(() => {
|
||||
let wordCount = 0;
|
||||
if (chapter.value.length != 1) return;
|
||||
for (let element of chapter.value[0].children) {
|
||||
wordCount += parseInt(element.getAttribute("wordCount")) + 1; //计算换行符
|
||||
if (wordCount - 1 >= pos) {
|
||||
//最后一段没有换行符
|
||||
jump(element, {
|
||||
duration: 0,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chapterRef.value.length === 1)
|
||||
chapterRef.value[0].scrollToReadedLength(pos);
|
||||
});
|
||||
};
|
||||
//计算当前章节阅读的字数
|
||||
const computeChapterPos = () => {
|
||||
if (chapter.value.length == 0) return;
|
||||
//计算当前阅读进度对应的element
|
||||
let chapterElement = chapter.value.find(
|
||||
(element) => element.getAttribute("chapterIndex") == chapterIndex.value
|
||||
const onReadedLengthChange = (index, pos) => {
|
||||
saveReadingBookProgressToBrowser(index, pos);
|
||||
};
|
||||
|
||||
// 文档标题
|
||||
watchEffect(() => {
|
||||
document.title = catalog.value[chapterIndex.value]?.title || document.title;
|
||||
});
|
||||
|
||||
// 阅读记录保存浏览器
|
||||
const saveReadingBookProgressToBrowser = (index, pos) => {
|
||||
//保存localStorage
|
||||
let bookUrl = sessionStorage.getItem("bookUrl");
|
||||
var book = JSON.parse(localStorage.getItem(bookUrl));
|
||||
book.index = index;
|
||||
book.chapterPos = pos;
|
||||
localStorage.setItem(bookUrl, JSON.stringify(book));
|
||||
//最近阅读
|
||||
book = JSON.parse(localStorage.getItem("readingRecent"));
|
||||
book.chapterIndex = index;
|
||||
book.chapterPos = pos;
|
||||
localStorage.setItem("readingRecent", JSON.stringify(book));
|
||||
//保存vuex
|
||||
chapterIndex.value = index;
|
||||
chapterPos.value = pos;
|
||||
//保存sessionStorage
|
||||
sessionStorage.setItem("chapterIndex", index);
|
||||
sessionStorage.setItem("chapterPos", String(pos));
|
||||
};
|
||||
|
||||
// 进度同步
|
||||
// 返回同步 同步请求会在获取书架前完成
|
||||
// 刷新 关闭页面
|
||||
const syncBookProgress = () => {
|
||||
console.log("page hide");
|
||||
if (!bookProgress.value) return;
|
||||
// 常规请求可能会被取消 使用Fetch keep-alive 或者 navigator.sendBeacon
|
||||
navigator.sendBeacon(
|
||||
`${import.meta.env.VITE_API || location.origin}/saveBookProgress`,
|
||||
JSON.stringify(bookProgress.value)
|
||||
);
|
||||
if (!chapterElement) return;
|
||||
//计算已读字数
|
||||
let mChapterPos = 0;
|
||||
for (let paragraph of chapterElement.children) {
|
||||
mChapterPos += parseInt(paragraph.getAttribute("wordCount")) + 1; //计算换行符
|
||||
if (paragraph.getBoundingClientRect().top >= 0) {
|
||||
chapterPos.value = mChapterPos - 1; //最后一段没有换行符
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
const bottom = ref();
|
||||
const toTop = () => {
|
||||
jump(top.value);
|
||||
};
|
||||
const toBottom = () => {
|
||||
jump(bottom.value);
|
||||
};
|
||||
|
||||
// 定时同步
|
||||
|
||||
// 章节切换
|
||||
const toNextChapter = () => {
|
||||
store.setContentLoading(true);
|
||||
let index = chapterIndex.value + 1;
|
||||
@@ -360,42 +370,33 @@ const toPreChapter = () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
const saveReadingBookProgressToBrowser = (index, pos = chapterPos.value) => {
|
||||
//保存localStorage
|
||||
let bookUrl = sessionStorage.getItem("bookUrl");
|
||||
var book = JSON.parse(localStorage.getItem(bookUrl));
|
||||
book.index = index;
|
||||
book.chapterPos = pos;
|
||||
localStorage.setItem(bookUrl, JSON.stringify(book));
|
||||
//最近阅读
|
||||
book = JSON.parse(localStorage.getItem("readingRecent"));
|
||||
book.chapterIndex = index;
|
||||
book.chapterPos = pos;
|
||||
localStorage.setItem("readingRecent", JSON.stringify(book));
|
||||
//保存vuex
|
||||
chapterIndex.value = index;
|
||||
chapterPos.value = pos;
|
||||
//保存sessionStorage
|
||||
sessionStorage.setItem("chapterIndex", index);
|
||||
sessionStorage.setItem("chapterPos", String(pos));
|
||||
};
|
||||
const updateChapterData = async (data, reloadChapter) => {
|
||||
if (reloadChapter) {
|
||||
chapterData.value.splice(0);
|
||||
|
||||
// 无限滚动
|
||||
const scrollObserve = ref(null);
|
||||
const loading = ref();
|
||||
watchEffect(() => {
|
||||
if (!infiniteLoading.value) {
|
||||
scrollObserve.value?.disconnect();
|
||||
} else {
|
||||
scrollObserve.value?.observe(loading.value);
|
||||
}
|
||||
chapterData.value.push(data);
|
||||
};
|
||||
});
|
||||
const loadMore = () => {
|
||||
let index = chapterData.value.slice(-1)[0].index;
|
||||
if (catalog.value.length - 1 > index) {
|
||||
getContent(index + 1, false);
|
||||
}
|
||||
};
|
||||
const router = useRouter();
|
||||
const toShelf = () => {
|
||||
router.push("/");
|
||||
// IntersectionObserver回调 底部加载
|
||||
const handleIScrollObserve = (entries) => {
|
||||
if (showLoading.value) return;
|
||||
for (let { isIntersecting } of entries) {
|
||||
if (!isIntersecting) return;
|
||||
loadMore();
|
||||
}
|
||||
};
|
||||
//监听方向键
|
||||
|
||||
// 监听方向键
|
||||
const handleKeyPress = (event) => {
|
||||
switch (event.key) {
|
||||
case "ArrowLeft":
|
||||
@@ -438,52 +439,6 @@ const handleKeyPress = (event) => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
//IntersectionObserver回调 底部加载
|
||||
const handleIScrollObserve = (entries) => {
|
||||
if (showLoading.value) return;
|
||||
for (let { isIntersecting } of entries) {
|
||||
if (!isIntersecting) return;
|
||||
loadMore();
|
||||
}
|
||||
};
|
||||
//IntersectionObserver回调 当前阅读章节序号
|
||||
const handleIReadingObserve = (entries) => {
|
||||
nextTick(() => {
|
||||
for (let { isIntersecting, target, boundingClientRect } of entries) {
|
||||
let chapterTitleIndex = parseInt(target.getAttribute("chapterIndex"));
|
||||
if (isIntersecting) {
|
||||
chapterIndex.value = chapterTitleIndex;
|
||||
} else {
|
||||
if (boundingClientRect.top < 0) {
|
||||
chapterIndex.value = chapterTitleIndex + 1;
|
||||
} else {
|
||||
chapterIndex.value = chapterTitleIndex - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
//添加所有章节到observe
|
||||
const addReadingObserve = () => {
|
||||
nextTick(() => {
|
||||
let chapterElements = chapter.value;
|
||||
if (!chapterElements) return;
|
||||
chapterElements.forEach((el) => readingObserve.value.observe(el));
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
computeChapterPos();
|
||||
saveReadingBookProgressToBrowser(chapterIndex.value);
|
||||
next();
|
||||
});
|
||||
/*
|
||||
window.addEventListener("beforeunload", (e) => {
|
||||
e.preventDefault();
|
||||
e.returnValue = "";
|
||||
alert(111);
|
||||
});
|
||||
*/
|
||||
|
||||
onMounted(() => {
|
||||
showLoading.value = true;
|
||||
@@ -523,13 +478,12 @@ onMounted(() => {
|
||||
|
||||
getContent(chapterIndex, true, chapterPos);
|
||||
window.addEventListener("keyup", handleKeyPress);
|
||||
window.addEventListener("visibilitychange", syncBookProgress);
|
||||
//监听底部加载
|
||||
scrollObserve.value = new IntersectionObserver(handleIScrollObserve, {
|
||||
rootMargin: "-100% 0% 20% 0%",
|
||||
});
|
||||
infiniteLoading.value && scrollObserve.value.observe(loading.value);
|
||||
//监听当前阅读章节
|
||||
readingObserve.value = new IntersectionObserver(handleIReadingObserve);
|
||||
//第二次点击同一本书 页面标题不会变化
|
||||
document.title = null;
|
||||
document.title = bookName + " | " + catalog.value[chapterIndex].title;
|
||||
@@ -544,10 +498,10 @@ onMounted(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("keyup", handleKeyPress);
|
||||
window.removeEventListener("visibilitychange", syncBookProgress);
|
||||
readSettingsVisible.value = false;
|
||||
popCataVisible.value = false;
|
||||
scrollObserve.value?.disconnect();
|
||||
readingObserve.value?.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ const toDetail = (bookUrl, bookName, bookAuthor, chapterIndex, chapterPos) => {
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(() => {
|
||||
//获取最近阅读书籍
|
||||
let readingRecentStr = localStorage.getItem("readingRecent");
|
||||
if (readingRecentStr != null) {
|
||||
@@ -197,8 +197,10 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
showLoading.value = true;
|
||||
await store.saveBookProcess();
|
||||
fetchBookShelfData();
|
||||
store
|
||||
.saveBookProgress()
|
||||
//确保各种网络情况下同步请求先完成
|
||||
.finally(fetchBookShelfData);
|
||||
});
|
||||
const fetchBookShelfData = () => {
|
||||
API.getBookShelf()
|
||||
|
||||
Reference in New Issue
Block a user